3">113 113
       @event.payload['foo']['bar']['baz'] = "a b"
114
-      lambda {
114
+      expect {
115 115
         @checker.receive([@event])
116
-      }.should change { Event.count }.by(1)
116
+      }.to change { Event.count }.by(1)
117 117
     end
118 118
 
119 119
     it "handles negated regex" do
@@ -124,14 +124,14 @@ describe Agents::TriggerAgent do
124 124
         'path' => "foo.bar.baz",
125 125
       }
126 126
 
127
-      lambda {
127
+      expect {
128 128
         @checker.receive([@event])
129
-      }.should_not change { Event.count }
129
+      }.not_to change { Event.count }
130 130
 
131 131
       @event.payload['foo']['bar']['baz'] = "a22b"
132
-      lambda {
132
+      expect {
133 133
         @checker.receive([@event])
134
-      }.should change { Event.count }.by(1)
134
+      }.to change { Event.count }.by(1)
135 135
     end
136 136
 
137 137
     it "handles array of negated regex" do
@@ -142,19 +142,19 @@ describe Agents::TriggerAgent do
142 142
         'path' => "foo.bar.baz",
143 143
       }
144 144
 
145
-      lambda {
145
+      expect {
146 146
         @checker.receive([@event])
147
-      }.should_not change { Event.count }
147
+      }.not_to change { Event.count }
148 148
 
149 149
       @event.payload['foo']['bar']['baz'] = "a3b"
150
-      lambda {
150
+      expect {
151 151
         @checker.receive([@event])
152
-      }.should change { Event.count }.by(1)
152
+      }.to change { Event.count }.by(1)
153 153
     end
154 154
 
155 155
     it "puts can extract values into the message based on paths" do
156 156
       @checker.receive([@event])
157
-      Event.last.payload['message'].should == "I saw 'a2b' from Joe"
157
+      expect(Event.last.payload['message']).to eq("I saw 'a2b' from Joe")
158 158
     end
159 159
 
160 160
     it "handles numerical comparisons" do
@@ -162,14 +162,14 @@ describe Agents::TriggerAgent do
162 162
       @checker.options['rules'].first['value'] = 6
163 163
       @checker.options['rules'].first['type'] = "field<value"
164 164
 
165
-      lambda {
165
+      expect {
166 166
         @checker.receive([@event])
167
-      }.should change { Event.count }.by(1)
167
+      }.to change { Event.count }.by(1)
168 168
 
169 169
       @checker.options['rules'].first['value'] = 3
170
-      lambda {
170
+      expect {
171 171
         @checker.receive([@event])
172
-      }.should_not change { Event.count }
172
+      }.not_to change { Event.count }
173 173
     end
174 174
 
175 175
     it "handles array of numerical comparisons" do
@@ -177,14 +177,14 @@ describe Agents::TriggerAgent do
177 177
       @checker.options['rules'].first['value'] = [6, 3]
178 178
       @checker.options['rules'].first['type'] = "field<value"
179 179
 
180
-      lambda {
180
+      expect {
181 181
         @checker.receive([@event])
182
-      }.should change { Event.count }.by(1)
182
+      }.to change { Event.count }.by(1)
183 183
 
184 184
       @checker.options['rules'].first['value'] = [4, 3]
185
-      lambda {
185
+      expect {
186 186
         @checker.receive([@event])
187
-      }.should_not change { Event.count }
187
+      }.not_to change { Event.count }
188 188
     end
189 189
 
190 190
     it "handles exact comparisons" do
@@ -192,14 +192,14 @@ describe Agents::TriggerAgent do
192 192
       @checker.options['rules'].first['type'] = "field==value"
193 193
 
194 194
       @checker.options['rules'].first['value'] = "hello there"
195
-      lambda {
195
+      expect {
196 196
         @checker.receive([@event])
197
-      }.should_not change { Event.count }
197
+      }.not_to change { Event.count }
198 198
 
199 199
       @checker.options['rules'].first['value'] = "hello world"
200
-      lambda {
200
+      expect {
201 201
         @checker.receive([@event])
202
-      }.should change { Event.count }.by(1)
202
+      }.to change { Event.count }.by(1)
203 203
     end
204 204
 
205 205
     it "handles array of exact comparisons" do
@@ -207,14 +207,14 @@ describe Agents::TriggerAgent do
207 207
       @checker.options['rules'].first['type'] = "field==value"
208 208
 
209 209
       @checker.options['rules'].first['value'] = ["hello there", "hello universe"]
210
-      lambda {
210
+      expect {
211 211
         @checker.receive([@event])
212
-      }.should_not change { Event.count }
212
+      }.not_to change { Event.count }
213 213
 
214 214
       @checker.options['rules'].first['value'] = ["hello world", "hello universe"]
215
-      lambda {
215
+      expect {
216 216
         @checker.receive([@event])
217
-      }.should change { Event.count }.by(1)
217
+      }.to change { Event.count }.by(1)
218 218
     end
219 219
 
220 220
     it "handles negated comparisons" do
@@ -222,15 +222,15 @@ describe Agents::TriggerAgent do
222 222
       @checker.options['rules'].first['type'] = "field!=value"
223 223
       @checker.options['rules'].first['value'] = "hello world"
224 224
 
225
-      lambda {
225
+      expect {
226 226
         @checker.receive([@event])
227
-      }.should_not change { Event.count }
227
+      }.not_to change { Event.count }
228 228
 
229 229
       @checker.options['rules'].first['value'] = "hello there"
230 230
 
231
-      lambda {
231
+      expect {
232 232
         @checker.receive([@event])
233
-      }.should change { Event.count }.by(1)
233
+      }.to change { Event.count }.by(1)
234 234
     end
235 235
 
236 236
     it "handles array of negated comparisons" do
@@ -238,15 +238,15 @@ describe Agents::TriggerAgent do
238 238
       @checker.options['rules'].first['type'] = "field!=value"
239 239
       @checker.options['rules'].first['value'] = ["hello world", "hello world"]
240 240
 
241
-      lambda {
241
+      expect {
242 242
         @checker.receive([@event])
243
-      }.should_not change { Event.count }
243
+      }.not_to change { Event.count }
244 244
 
245 245
       @checker.options['rules'].first['value'] = ["hello there", "hello world"]
246 246
 
247
-      lambda {
247
+      expect {
248 248
         @checker.receive([@event])
249
-      }.should change { Event.count }.by(1)
249
+      }.to change { Event.count }.by(1)
250 250
     end
251 251
 
252 252
     it "does fine without dots in the path" do
@@ -254,19 +254,19 @@ describe Agents::TriggerAgent do
254 254
       @checker.options['rules'].first['type'] = "field==value"
255 255
       @checker.options['rules'].first['path'] = "hello"
256 256
       @checker.options['rules'].first['value'] = "world"
257
-      lambda {
257
+      expect {
258 258
         @checker.receive([@event])
259
-      }.should change { Event.count }.by(1)
259
+      }.to change { Event.count }.by(1)
260 260
 
261 261
       @checker.options['rules'].first['path'] = "foo"
262
-      lambda {
262
+      expect {
263 263
         @checker.receive([@event])
264
-      }.should_not change { Event.count }
264
+      }.not_to change { Event.count }
265 265
 
266 266
       @checker.options['rules'].first['value'] = "hi"
267
-      lambda {
267
+      expect {
268 268
         @checker.receive([@event])
269
-      }.should_not change { Event.count }
269
+      }.not_to change { Event.count }
270 270
     end
271 271
 
272 272
     it "handles multiple events" do
@@ -278,9 +278,9 @@ describe Agents::TriggerAgent do
278 278
       event3.agent = agents(:bob_weather_agent)
279 279
       event3.payload = { 'foo' => { 'bar' => { 'baz' => "a222b" }}}
280 280
 
281
-      lambda {
281
+      expect {
282 282
         @checker.receive([@event, event2, event3])
283
-      }.should change { Event.count }.by(2)
283
+      }.to change { Event.count }.by(2)
284 284
     end
285 285
 
286 286
     it "handles ANDing rules together" do
@@ -292,14 +292,14 @@ describe Agents::TriggerAgent do
292 292
 
293 293
       @event.payload['foo']["bing"] = "5"
294 294
 
295
-      lambda {
295
+      expect {
296 296
         @checker.receive([@event])
297
-      }.should change { Event.count }.by(1)
297
+      }.to change { Event.count }.by(1)
298 298
 
299 299
       @checker.options['rules'].last['value'] = 6
300
-      lambda {
300
+      expect {
301 301
         @checker.receive([@event])
302
-      }.should_not change { Event.count }
302
+      }.not_to change { Event.count }
303 303
     end
304 304
 
305 305
     describe "when 'keep_event' is true" do
@@ -314,16 +314,16 @@ describe Agents::TriggerAgent do
314 314
         @checker.options['message'] = ''
315 315
         @event.payload['message'] = 'hi there'
316 316
 
317
-        lambda {
317
+        expect {
318 318
           @checker.receive([@event])
319
-        }.should_not change { Event.count }
319
+        }.not_to change { Event.count }
320 320
 
321 321
         @checker.options['rules'].first['value'] = 6
322
-        lambda {
322
+        expect {
323 323
           @checker.receive([@event])
324
-        }.should change { Event.count }.by(1)
324
+        }.to change { Event.count }.by(1)
325 325
 
326
-        @checker.most_recent_event.payload.should == @event.payload
326
+        expect(@checker.most_recent_event.payload).to eq(@event.payload)
327 327
       end
328 328
 
329 329
       it "merges 'message' into the original event when present" do
@@ -331,8 +331,8 @@ describe Agents::TriggerAgent do
331 331
 
332 332
         @checker.receive([@event])
333 333
 
334
-        @checker.most_recent_event.payload.should == @event.payload.merge(:message => "I saw '5' from Joe")
334
+        expect(@checker.most_recent_event.payload).to eq(@event.payload.merge(:message => "I saw '5' from Joe"))
335 335
       end
336 336
     end
337 337
   end
338
-end
338
+end

+ 3 - 3
spec/models/agents/tumblr_publish_agent_spec.rb

@@ -30,9 +30,9 @@ describe Agents::TumblrPublishAgent do
30 30
   describe '#receive' do
31 31
     it 'should publish any payload it receives' do
32 32
       Agents::TumblrPublishAgent.async_receive(@checker.id, [@event.id])
33
-      @checker.events.count.should eq(1)
34
-      @checker.events.first.payload['post_id'].should eq('5')
35
-      @checker.events.first.payload['published_post'].should eq('[huginnbot.tumblr.com] text')
33
+      expect(@checker.events.count).to eq(1)
34
+      expect(@checker.events.first.payload['post_id']).to eq('5')
35
+      expect(@checker.events.first.payload['published_post']).to eq('[huginnbot.tumblr.com] text')
36 36
     end
37 37
   end
38 38
 end

+ 13 - 13
spec/models/agents/twilio_agent_spec.rb

@@ -37,62 +37,62 @@ describe Agents::TwilioAgent do
37 37
       event2.save!
38 38
 
39 39
       @checker.receive([@event,event1,event2])
40
-      @sent_messages.should == ['Looks like its going to rain','Some message','Some other message']
40
+      expect(@sent_messages).to eq(['Looks like its going to rain','Some message','Some other message'])
41 41
     end
42 42
 
43 43
     it 'should check if receive_text is working fine' do
44 44
       @checker.options[:receive_text] = 'false'
45 45
       @checker.receive([@event])
46
-      @sent_messages.should be_empty
46
+      expect(@sent_messages).to be_empty
47 47
     end
48 48
 
49 49
     it 'should check if receive_call is working fine' do
50 50
       @checker.options[:receive_call] = 'true'
51 51
       @checker.receive([@event])
52
-      @checker.memory[:pending_calls].should_not == {}
52
+      expect(@checker.memory[:pending_calls]).not_to eq({})
53 53
     end
54 54
 
55 55
   end
56 56
 
57 57
   describe '#working?' do
58 58
     it 'checks if events have been received within the expected receive period' do
59
-      @checker.should_not be_working # No events received
59
+      expect(@checker).not_to be_working # No events received
60 60
       Agents::TwilioAgent.async_receive @checker.id, [@event.id]
61
-      @checker.reload.should be_working # Just received events
61
+      expect(@checker.reload).to be_working # Just received events
62 62
       two_days_from_now = 2.days.from_now
63 63
       stub(Time).now { two_days_from_now }
64
-      @checker.reload.should_not be_working # More time has passed than the expected receive period without any new events
64
+      expect(@checker.reload).not_to be_working # More time has passed than the expected receive period without any new events
65 65
     end
66 66
   end
67 67
 
68 68
   describe "validation" do
69 69
     before do
70
-      @checker.should be_valid
70
+      expect(@checker).to be_valid
71 71
     end
72 72
 
73 73
     it "should validate presence of of account_sid" do
74 74
       @checker.options[:account_sid] = ""
75
-      @checker.should_not be_valid
75
+      expect(@checker).not_to be_valid
76 76
     end
77 77
 
78 78
     it "should validate presence of auth_token" do
79 79
       @checker.options[:auth_token] = ""
80
-      @checker.should_not be_valid
80
+      expect(@checker).not_to be_valid
81 81
     end
82 82
 
83 83
     it "should validate presence of receiver_cell" do
84 84
       @checker.options[:receiver_cell] = ""
85
-      @checker.should_not be_valid
85
+      expect(@checker).not_to be_valid
86 86
     end
87 87
 
88 88
     it "should validate presence of sender_cell" do
89 89
       @checker.options[:sender_cell] = ""
90
-      @checker.should_not be_valid
90
+      expect(@checker).not_to be_valid
91 91
     end
92 92
 
93 93
     it "should make sure filling sure filling server_url is not necessary" do
94 94
       @checker.options[:server_url] = ""
95
-      @checker.should be_valid
95
+      expect(@checker).to be_valid
96 96
     end
97 97
   end
98
-end
98
+end

+ 5 - 5
spec/models/agents/twitter_publish_agent_spec.rb

@@ -42,19 +42,19 @@ describe Agents::TwitterPublishAgent do
42 42
       event2.save!
43 43
 
44 44
       Agents::TwitterPublishAgent.async_receive(@checker.id, [event1.id, event2.id])
45
-      @sent_messages.count.should eq(2)
46
-      @checker.events.count.should eq(2)
45
+      expect(@sent_messages.count).to eq(2)
46
+      expect(@checker.events.count).to eq(2)
47 47
     end
48 48
   end
49 49
 
50 50
   describe '#working?' do
51 51
     it 'checks if events have been received within the expected receive period' do
52
-      @checker.should_not be_working # No events received
52
+      expect(@checker).not_to be_working # No events received
53 53
       Agents::TwitterPublishAgent.async_receive(@checker.id, [@event.id])
54
-      @checker.reload.should be_working # Just received events
54
+      expect(@checker.reload).to be_working # Just received events
55 55
       two_days_from_now = 2.days.from_now
56 56
       stub(Time).now { two_days_from_now }
57
-      @checker.reload.should_not be_working # More time has passed than the expected receive period without any new events
57
+      expect(@checker.reload).not_to be_working # More time has passed than the expected receive period without any new events
58 58
     end
59 59
   end
60 60
 end

+ 24 - 24
spec/models/agents/twitter_stream_agent_spec.rb

@@ -30,8 +30,8 @@ describe Agents::TwitterStreamAgent do
30 30
         @agent.process_tweet('keyword1', {:text => "something", :user => {:name => "Mr. Someone"}})
31 31
 
32 32
         @agent.reload
33
-        @agent.memory[:filter_counts][:keyword1].should == 2
34
-        @agent.memory[:filter_counts][:keyword2].should == 1
33
+        expect(@agent.memory[:filter_counts][:keyword1]).to eq(2)
34
+        expect(@agent.memory[:filter_counts][:keyword2]).to eq(1)
35 35
       end
36 36
 
37 37
       it 'records counts for keyword sets as well' do
@@ -46,44 +46,44 @@ describe Agents::TwitterStreamAgent do
46 46
         @agent.process_tweet('keyword1-1', {:text => "something", :user => {:name => "Mr. Someone"}})
47 47
 
48 48
         @agent.reload
49
-        @agent.memory[:filter_counts][:'keyword1-1'].should == 4 # it stores on the first keyword
50
-        @agent.memory[:filter_counts][:keyword2].should == 2
49
+        expect(@agent.memory[:filter_counts][:'keyword1-1']).to eq(4) # it stores on the first keyword
50
+        expect(@agent.memory[:filter_counts][:keyword2]).to eq(2)
51 51
       end
52 52
 
53 53
       it 'removes unused keys' do
54 54
         @agent.memory[:filter_counts] = {:keyword1 => 2, :keyword2 => 3, :keyword3 => 4}
55 55
         @agent.save!
56 56
         @agent.process_tweet('keyword1', {:text => "something", :user => {:name => "Mr. Someone"}})
57
-        @agent.reload.memory[:filter_counts].should == { 'keyword1' => 3, 'keyword2' => 3 }
57
+        expect(@agent.reload.memory[:filter_counts]).to eq({ 'keyword1' => 3, 'keyword2' => 3 })
58 58
       end
59 59
     end
60 60
 
61 61
     context "when generate is set to 'events'" do
62 62
       it 'emits events immediately' do
63
-        lambda {
63
+        expect {
64 64
           @agent.process_tweet('keyword1', {:text => "something", :user => {:name => "Mr. Someone"}})
65
-        }.should change { @agent.events.count }.by(1)
65
+        }.to change { @agent.events.count }.by(1)
66 66
 
67
-        @agent.events.last.payload.should == {
67
+        expect(@agent.events.last.payload).to eq({
68 68
           'filter' => 'keyword1',
69 69
           'text' => "something",
70 70
           'user' => { 'name' => "Mr. Someone" }
71
-        }
71
+        })
72 72
       end
73 73
 
74 74
       it 'handles keyword sets too' do
75 75
         @agent.options[:filters][0] = %w[keyword1-1 keyword1-2 keyword1-3]
76 76
         @agent.save!
77 77
 
78
-        lambda {
78
+        expect {
79 79
           @agent.process_tweet('keyword1-2', {:text => "something", :user => {:name => "Mr. Someone"}})
80
-        }.should change { @agent.events.count }.by(1)
80
+        }.to change { @agent.events.count }.by(1)
81 81
 
82
-        @agent.events.last.payload.should == {
82
+        expect(@agent.events.last.payload).to eq({
83 83
           'filter' => 'keyword1-1',
84 84
           'text' => "something",
85 85
           'user' => { 'name' => "Mr. Someone" }
86
-        }
86
+        })
87 87
       end
88 88
     end
89 89
   end
@@ -100,17 +100,17 @@ describe Agents::TwitterStreamAgent do
100 100
         @agent.process_tweet('keyword2', {:text => "something", :user => {:name => "Mr. Someone"}})
101 101
         @agent.process_tweet('keyword1', {:text => "something", :user => {:name => "Mr. Someone"}})
102 102
 
103
-        lambda {
103
+        expect {
104 104
           @agent.reload.check
105
-        }.should change { @agent.events.count }.by(2)
105
+        }.to change { @agent.events.count }.by(2)
106 106
 
107
-        @agent.events[-1].payload[:filter].should == 'keyword1'
108
-        @agent.events[-1].payload[:count].should == 2
107
+        expect(@agent.events[-1].payload[:filter]).to eq('keyword1')
108
+        expect(@agent.events[-1].payload[:count]).to eq(2)
109 109
 
110
-        @agent.events[-2].payload[:filter].should == 'keyword2'
111
-        @agent.events[-2].payload[:count].should == 1
110
+        expect(@agent.events[-2].payload[:filter]).to eq('keyword2')
111
+        expect(@agent.events[-2].payload[:count]).to eq(1)
112 112
 
113
-        @agent.memory[:filter_counts].should == {}
113
+        expect(@agent.memory[:filter_counts]).to eq({})
114 114
       end
115 115
     end
116 116
 
@@ -118,11 +118,11 @@ describe Agents::TwitterStreamAgent do
118 118
       it 'does nothing' do
119 119
         @agent.memory[:filter_counts] = { :keyword1 => 2 }
120 120
         @agent.save!
121
-        lambda {
121
+        expect {
122 122
           @agent.reload.check
123
-        }.should_not change { Event.count }
124
-        @agent.memory[:filter_counts].should == {}
123
+        }.not_to change { Event.count }
124
+        expect(@agent.memory[:filter_counts]).to eq({})
125 125
       end
126 126
     end
127 127
   end
128
-end
128
+end

+ 2 - 2
spec/models/agents/twitter_user_agent_spec.rb

@@ -23,7 +23,7 @@ describe Agents::TwitterUserAgent do
23 23
 
24 24
   describe "#check" do
25 25
     it "should check for changes" do
26
-      lambda { @checker.check }.should change { Event.count }.by(5)
26
+      expect { @checker.check }.to change { Event.count }.by(5)
27 27
     end
28 28
   end
29 29
 
@@ -36,7 +36,7 @@ describe Agents::TwitterUserAgent do
36 36
       checker.user = users(:bob)
37 37
       checker.save!
38 38
 
39
-      lambda { checker.check }.should change { Event.count }.by(0)
39
+      expect { checker.check }.to change { Event.count }.by(0)
40 40
     end
41 41
   end
42 42
 

+ 13 - 13
spec/models/agents/user_location_agent_spec.rb

@@ -12,37 +12,37 @@ describe Agents::UserLocationAgent do
12 12
     event.created_at = Time.now
13 13
     event.payload = { 'longitude' => 123, 'latitude' => 45, 'something' => 'else' }
14 14
 
15
-    lambda {
15
+    expect {
16 16
       @agent.receive([event])
17
-    }.should change { @agent.events.count }.by(1)
17
+    }.to change { @agent.events.count }.by(1)
18 18
 
19
-    @agent.events.last.payload.should == { 'longitude' => 123, 'latitude' => 45, 'something' => 'else' }
20
-    @agent.events.last.lat.should == 45
21
-    @agent.events.last.lng.should == 123
19
+    expect(@agent.events.last.payload).to eq({ 'longitude' => 123, 'latitude' => 45, 'something' => 'else' })
20
+    expect(@agent.events.last.lat).to eq(45)
21
+    expect(@agent.events.last.lng).to eq(123)
22 22
   end
23 23
 
24 24
   it 'does not accept a web request that is not POST' do
25 25
     %w[get put delete patch].each { |method|
26 26
       content, status, content_type = @agent.receive_web_request({ 'secret' => 'my_secret' }, method, 'application/json')
27
-      status.should == 404
27
+      expect(status).to eq(404)
28 28
     }
29 29
   end
30 30
 
31 31
   it 'requires a valid secret for a web request' do
32 32
     content, status, content_type = @agent.receive_web_request({ 'secret' => 'fake' }, 'post', 'application/json')
33
-    status.should == 401
33
+    expect(status).to eq(401)
34 34
 
35 35
     content, status, content_type = @agent.receive_web_request({ 'secret' => 'my_secret' }, 'post', 'application/json')
36
-    status.should == 200
36
+    expect(status).to eq(200)
37 37
   end
38 38
 
39 39
   it 'creates an event on a web request' do
40
-    lambda {
40
+    expect {
41 41
       @agent.receive_web_request({ 'secret' => 'my_secret', 'longitude' => 123, 'latitude' => 45, 'something' => 'else' }, 'post', 'application/json')
42
-    }.should change { @agent.events.count }.by(1)
42
+    }.to change { @agent.events.count }.by(1)
43 43
 
44
-    @agent.events.last.payload.should == { 'longitude' => 123, 'latitude' => 45, 'something' => 'else' }
45
-    @agent.events.last.lat.should == 45
46
-    @agent.events.last.lng.should == 123
44
+    expect(@agent.events.last.payload).to eq({ 'longitude' => 123, 'latitude' => 45, 'something' => 'else' })
45
+    expect(@agent.events.last.lat).to eq(45)
46
+    expect(@agent.events.last.lng).to eq(123)
47 47
   end
48 48
 end

+ 10 - 10
spec/models/agents/webhook_agent_spec.rb

@@ -13,27 +13,27 @@ describe Agents::WebhookAgent do
13 13
   describe 'receive_web_request' do
14 14
     it 'should create event if secret matches' do
15 15
       out = nil
16
-      lambda {
16
+      expect {
17 17
         out = agent.receive_web_request({ 'secret' => 'foobar', 'payload' => payload }, "post", "text/html")
18
-      }.should change { Event.count }.by(1)
19
-      out.should eq(['Event Created', 201])
20
-      Event.last.payload.should eq(payload)
18
+      }.to change { Event.count }.by(1)
19
+      expect(out).to eq(['Event Created', 201])
20
+      expect(Event.last.payload).to eq(payload)
21 21
     end
22 22
 
23 23
     it 'should not create event if secrets dont match' do
24 24
       out = nil
25
-      lambda {
25
+      expect {
26 26
         out = agent.receive_web_request({ 'secret' => 'bazbat', 'payload' => payload }, "post", "text/html")
27
-      }.should change { Event.count }.by(0)
28
-      out.should eq(['Not Authorized', 401])
27
+      }.to change { Event.count }.by(0)
28
+      expect(out).to eq(['Not Authorized', 401])
29 29
     end
30 30
 
31 31
     it "should only accept POSTs" do
32 32
       out = nil
33
-      lambda {
33
+      expect {
34 34
         out = agent.receive_web_request({ 'secret' => 'foobar', 'payload' => payload }, "get", "text/html")
35
-      }.should change { Event.count }.by(0)
36
-      out.should eq(['Please use POST requests only', 401])
35
+      }.to change { Event.count }.by(0)
36
+      expect(out).to eq(['Please use POST requests only', 401])
37 37
     end
38 38
   end
39 39
 end

+ 80 - 80
spec/models/agents/website_agent_spec.rb

@@ -29,71 +29,71 @@ describe Agents::WebsiteAgent do
29 29
 
30 30
     describe "validations" do
31 31
       before do
32
-        @checker.should be_valid
32
+        expect(@checker).to be_valid
33 33
       end
34 34
 
35 35
       it "should validate the integer fields" do
36 36
         @checker.options['expected_update_period_in_days'] = "2"
37
-        @checker.should be_valid
37
+        expect(@checker).to be_valid
38 38
 
39 39
         @checker.options['expected_update_period_in_days'] = "nonsense"
40
-        @checker.should_not be_valid
40
+        expect(@checker).not_to be_valid
41 41
       end
42 42
 
43 43
       it "should validate uniqueness_look_back" do
44 44
         @checker.options['uniqueness_look_back'] = "nonsense"
45
-        @checker.should_not be_valid
45
+        expect(@checker).not_to be_valid
46 46
 
47 47
         @checker.options['uniqueness_look_back'] = "2"
48
-        @checker.should be_valid
48
+        expect(@checker).to be_valid
49 49
       end
50 50
 
51 51
       it "should validate mode" do
52 52
         @checker.options['mode'] = "nonsense"
53
-        @checker.should_not be_valid
53
+        expect(@checker).not_to be_valid
54 54
 
55 55
         @checker.options['mode'] = "on_change"
56
-        @checker.should be_valid
56
+        expect(@checker).to be_valid
57 57
 
58 58
         @checker.options['mode'] = "all"
59
-        @checker.should be_valid
59
+        expect(@checker).to be_valid
60 60
 
61 61
         @checker.options['mode'] = ""
62
-        @checker.should be_valid
62
+        expect(@checker).to be_valid
63 63
       end
64 64
 
65 65
       it "should validate the force_encoding option" do
66 66
         @checker.options['force_encoding'] = ''
67
-        @checker.should be_valid
67
+        expect(@checker).to be_valid
68 68
 
69 69
         @checker.options['force_encoding'] = 'UTF-8'
70
-        @checker.should be_valid
70
+        expect(@checker).to be_valid
71 71
 
72 72
         @checker.options['force_encoding'] = ['UTF-8']
73
-        @checker.should_not be_valid
73
+        expect(@checker).not_to be_valid
74 74
 
75 75
         @checker.options['force_encoding'] = 'UTF-42'
76
-        @checker.should_not be_valid
76
+        expect(@checker).not_to be_valid
77 77
       end
78 78
     end
79 79
 
80 80
     describe "#check" do
81 81
       it "should check for changes (and update Event.expires_at)" do
82
-        lambda { @checker.check }.should change { Event.count }.by(1)
82
+        expect { @checker.check }.to change { Event.count }.by(1)
83 83
         event = Event.last
84 84
         sleep 2
85
-        lambda { @checker.check }.should_not change { Event.count }
85
+        expect { @checker.check }.not_to change { Event.count }
86 86
         update_event = Event.last
87
-        update_event.expires_at.should_not == event.expires_at
87
+        expect(update_event.expires_at).not_to eq(event.expires_at)
88 88
       end
89 89
 
90 90
       it "should always save events when in :all mode" do
91
-        lambda {
91
+        expect {
92 92
           @valid_options['mode'] = 'all'
93 93
           @checker.options = @valid_options
94 94
           @checker.check
95 95
           @checker.check
96
-        }.should change { Event.count }.by(2)
96
+        }.to change { Event.count }.by(2)
97 97
       end
98 98
 
99 99
       it "should take uniqueness_look_back into account during deduplication" do
@@ -105,50 +105,50 @@ describe Agents::WebsiteAgent do
105 105
         event.payload = "{}"
106 106
         event.save
107 107
 
108
-        lambda {
108
+        expect {
109 109
           @valid_options['mode'] = 'on_change'
110 110
           @valid_options['uniqueness_look_back'] = 2
111 111
           @checker.options = @valid_options
112 112
           @checker.check
113
-        }.should_not change { Event.count }
113
+        }.not_to change { Event.count }
114 114
 
115
-        lambda {
115
+        expect {
116 116
           @valid_options['mode'] = 'on_change'
117 117
           @valid_options['uniqueness_look_back'] = 1
118 118
           @checker.options = @valid_options
119 119
           @checker.check
120
-        }.should change { Event.count }.by(1)
120
+        }.to change { Event.count }.by(1)
121 121
       end
122 122
 
123 123
       it "should log an error if the number of results for a set of extraction patterns differs" do
124 124
         @valid_options['extract']['url']['css'] = "div"
125 125
         @checker.options = @valid_options
126 126
         @checker.check
127
-        @checker.logs.first.message.should =~ /Got an uneven number of matches/
127
+        expect(@checker.logs.first.message).to match(/Got an uneven number of matches/)
128 128
       end
129 129
 
130 130
       it "should accept an array for url" do
131 131
         @valid_options['url'] = ["http://xkcd.com/1/", "http://xkcd.com/2/"]
132 132
         @checker.options = @valid_options
133
-        lambda { @checker.save! }.should_not raise_error;
134
-        lambda { @checker.check }.should_not raise_error;
133
+        expect { @checker.save! }.not_to raise_error;
134
+        expect { @checker.check }.not_to raise_error;
135 135
       end
136 136
 
137 137
       it "should parse events from all urls in array" do
138
-        lambda {
138
+        expect {
139 139
           @valid_options['url'] = ["http://xkcd.com/", "http://xkcd.com/"]
140 140
           @valid_options['mode'] = 'all'
141 141
           @checker.options = @valid_options
142 142
           @checker.check
143
-        }.should change { Event.count }.by(2)
143
+        }.to change { Event.count }.by(2)
144 144
       end
145 145
 
146 146
       it "should follow unique rules when parsing array of urls" do
147
-        lambda {
147
+        expect {
148 148
           @valid_options['url'] = ["http://xkcd.com/", "http://xkcd.com/"]
149 149
           @checker.options = @valid_options
150 150
           @checker.check
151
-        }.should change { Event.count }.by(1)
151
+        }.to change { Event.count }.by(1)
152 152
       end
153 153
     end
154 154
 
@@ -177,7 +177,7 @@ describe Agents::WebsiteAgent do
177 177
 
178 178
         checker.check
179 179
         event = Event.last
180
-        event.payload['value'].should == huginn
180
+        expect(event.payload['value']).to eq(huginn)
181 181
       end
182 182
 
183 183
       it 'should be overridden with force_encoding option' do
@@ -204,7 +204,7 @@ describe Agents::WebsiteAgent do
204 204
 
205 205
         checker.check
206 206
         event = Event.last
207
-        event.payload['value'].should == huginn
207
+        expect(event.payload['value']).to eq(huginn)
208 208
       end
209 209
     end
210 210
 
@@ -213,20 +213,20 @@ describe Agents::WebsiteAgent do
213 213
         stubbed_time = Time.now
214 214
         stub(Time).now { stubbed_time }
215 215
 
216
-        @checker.should_not be_working # No events created
216
+        expect(@checker).not_to be_working # No events created
217 217
         @checker.check
218
-        @checker.reload.should be_working # Just created events
218
+        expect(@checker.reload).to be_working # Just created events
219 219
 
220 220
         @checker.error "oh no!"
221
-        @checker.reload.should_not be_working # There is a recent error
221
+        expect(@checker.reload).not_to be_working # There is a recent error
222 222
 
223 223
         stubbed_time = 20.minutes.from_now
224 224
         @checker.events.delete_all
225 225
         @checker.check
226
-        @checker.reload.should be_working # There is a newer event now
226
+        expect(@checker.reload).to be_working # There is a newer event now
227 227
 
228 228
         stubbed_time = 2.days.from_now
229
-        @checker.reload.should_not be_working # Two days have passed without a new event having been created
229
+        expect(@checker.reload).not_to be_working # Two days have passed without a new event having been created
230 230
       end
231 231
     end
232 232
 
@@ -234,9 +234,9 @@ describe Agents::WebsiteAgent do
234 234
       it "parses CSS" do
235 235
         @checker.check
236 236
         event = Event.last
237
-        event.payload['url'].should == "http://imgs.xkcd.com/comics/evolving.png"
238
-        event.payload['title'].should == "Evolving"
239
-        event.payload['hovertext'].should =~ /^Biologists play reverse/
237
+        expect(event.payload['url']).to eq("http://imgs.xkcd.com/comics/evolving.png")
238
+        expect(event.payload['title']).to eq("Evolving")
239
+        expect(event.payload['hovertext']).to match(/^Biologists play reverse/)
240 240
       end
241 241
 
242 242
       it "parses XPath" do
@@ -247,9 +247,9 @@ describe Agents::WebsiteAgent do
247 247
         @checker.options = @valid_options
248 248
         @checker.check
249 249
         event = Event.last
250
-        event.payload['url'].should == "http://imgs.xkcd.com/comics/evolving.png"
251
-        event.payload['title'].should == "Evolving"
252
-        event.payload['hovertext'].should =~ /^Biologists play reverse/
250
+        expect(event.payload['url']).to eq("http://imgs.xkcd.com/comics/evolving.png")
251
+        expect(event.payload['title']).to eq("Evolving")
252
+        expect(event.payload['hovertext']).to match(/^Biologists play reverse/)
253 253
       end
254 254
 
255 255
       it "should turn relative urls to absolute" do
@@ -268,7 +268,7 @@ describe Agents::WebsiteAgent do
268 268
         rel.save!
269 269
         rel.check
270 270
         event = Event.last
271
-        event.payload['url'].should == "http://xkcd.com/about"
271
+        expect(event.payload['url']).to eq("http://xkcd.com/about")
272 272
       end
273 273
 
274 274
       it "should return an integer value if XPath evaluates to one" do
@@ -287,7 +287,7 @@ describe Agents::WebsiteAgent do
287 287
         rel.save!
288 288
         rel.check
289 289
         event = Event.last
290
-        event.payload['num_links'].should == "9"
290
+        expect(event.payload['num_links']).to eq("9")
291 291
       end
292 292
 
293 293
       it "should return all texts concatenated if XPath returns many text nodes" do
@@ -306,7 +306,7 @@ describe Agents::WebsiteAgent do
306 306
         rel.save!
307 307
         rel.check
308 308
         event = Event.last
309
-        event.payload['slogan'].should == "A webcomic of romance, sarcasm, math, and language."
309
+        expect(event.payload['slogan']).to eq("A webcomic of romance, sarcasm, math, and language.")
310 310
       end
311 311
 
312 312
       it "should interpolate _response_" do
@@ -317,7 +317,7 @@ describe Agents::WebsiteAgent do
317 317
         @checker.options = @valid_options
318 318
         @checker.check
319 319
         event = Event.last
320
-        event.payload['response_info'].should == 'The reponse was 200 OK.'
320
+        expect(event.payload['response_info']).to eq('The reponse was 200 OK.')
321 321
       end
322 322
 
323 323
       describe "JSON" do
@@ -346,8 +346,8 @@ describe Agents::WebsiteAgent do
346 346
 
347 347
           checker.check
348 348
           event = Event.last
349
-          event.payload['version'].should == 2
350
-          event.payload['title'].should == "hello!"
349
+          expect(event.payload['version']).to eq(2)
350
+          expect(event.payload['title']).to eq("hello!")
351 351
         end
352 352
 
353 353
         it "can handle arrays" do
@@ -375,17 +375,17 @@ describe Agents::WebsiteAgent do
375 375
           checker.user = users(:bob)
376 376
           checker.save!
377 377
 
378
-          lambda {
378
+          expect {
379 379
             checker.check
380
-          }.should change { Event.count }.by(2)
380
+          }.to change { Event.count }.by(2)
381 381
 
382 382
           event = Event.all[-1]
383
-          event.payload['version'].should == 2.5
384
-          event.payload['title'].should == "second"
383
+          expect(event.payload['version']).to eq(2.5)
384
+          expect(event.payload['title']).to eq("second")
385 385
 
386 386
           event = Event.all[-2]
387
-          event.payload['version'].should == 2
388
-          event.payload['title'].should == "first"
387
+          expect(event.payload['version']).to eq(2)
388
+          expect(event.payload['title']).to eq("first")
389 389
         end
390 390
 
391 391
         it "stores the whole object if :extract is not specified" do
@@ -409,8 +409,8 @@ describe Agents::WebsiteAgent do
409 409
 
410 410
           checker.check
411 411
           event = Event.last
412
-          event.payload['response']['version'].should == 2
413
-          event.payload['response']['title'].should == "hello!"
412
+          expect(event.payload['response']['version']).to eq(2)
413
+          expect(event.payload['response']['title']).to eq("hello!")
414 414
         end
415 415
       end
416 416
 
@@ -442,27 +442,27 @@ fire: hot
442 442
             'property' => { 'regexp' => '^(?<word>.+?): (?<property>.+)$', index: 'property' },
443 443
           })
444 444
 
445
-          lambda {
445
+          expect {
446 446
             @checker.check
447
-          }.should change { Event.count }.by(2)
447
+          }.to change { Event.count }.by(2)
448 448
 
449 449
           event1, event2 = Event.last(2)
450
-          event1.payload['word'].should == 'water'
451
-          event1.payload['property'].should == 'wet'
452
-          event2.payload['word'].should == 'fire'
453
-          event2.payload['property'].should == 'hot'
450
+          expect(event1.payload['word']).to eq('water')
451
+          expect(event1.payload['property']).to eq('wet')
452
+          expect(event2.payload['word']).to eq('fire')
453
+          expect(event2.payload['property']).to eq('hot')
454 454
         end
455 455
 
456 456
         it "works with regexp with named capture" do
457
-          lambda {
457
+          expect {
458 458
             @checker.check
459
-          }.should change { Event.count }.by(2)
459
+          }.to change { Event.count }.by(2)
460 460
 
461 461
           event1, event2 = Event.last(2)
462
-          event1.payload['word'].should == 'water'
463
-          event1.payload['property'].should == 'wet'
464
-          event2.payload['word'].should == 'fire'
465
-          event2.payload['property'].should == 'hot'
462
+          expect(event1.payload['word']).to eq('water')
463
+          expect(event1.payload['property']).to eq('wet')
464
+          expect(event2.payload['word']).to eq('fire')
465
+          expect(event2.payload['property']).to eq('hot')
466 466
         end
467 467
       end
468 468
     end
@@ -478,14 +478,14 @@ fire: hot
478 478
       end
479 479
 
480 480
       it "should scrape from the url element in incoming event payload" do
481
-        lambda {
481
+        expect {
482 482
           @checker.options = @valid_options
483 483
           @checker.receive([@event])
484
-        }.should change { Event.count }.by(1)
484
+        }.to change { Event.count }.by(1)
485 485
       end
486 486
 
487 487
       it "should interpolate values from incoming event payload" do
488
-        lambda {
488
+        expect {
489 489
           @valid_options['extract'] = {
490 490
             'from' => {
491 491
               'xpath' => '*[1]',
@@ -498,18 +498,18 @@ fire: hot
498 498
           }
499 499
           @checker.options = @valid_options
500 500
           @checker.receive([@event])
501
-        }.should change { Event.count }.by(1)
501
+        }.to change { Event.count }.by(1)
502 502
 
503
-        Event.last.payload.should == {
503
+        expect(Event.last.payload).to eq({
504 504
           'from' => 'http://xkcd.com',
505 505
           'to' => 'http://dynamic.xkcd.com/random/comic/',
506
-        }
506
+        })
507 507
       end
508 508
 
509 509
       it "should interpolate values from incoming event payload and _response_" do
510 510
         @event.payload['title'] = 'XKCD'
511 511
 
512
-        lambda {
512
+        expect {
513 513
           @valid_options['extract'] = {
514 514
             'response_info' => @valid_options['extract']['url'].merge(
515 515
               'value' => '{% capture sentence %}The reponse from {{title}} was {{_response_.status}} {{_response_.headers.X-Status-Message}}.{% endcapture %}{{sentence | to_xpath}}'
@@ -517,9 +517,9 @@ fire: hot
517 517
           }
518 518
           @checker.options = @valid_options
519 519
           @checker.receive([@event])
520
-        }.should change { Event.count }.by(1)
520
+        }.to change { Event.count }.by(1)
521 521
 
522
-        Event.last.payload['response_info'].should == 'The reponse from XKCD was 200 OK.'
522
+        expect(Event.last.payload['response_info']).to eq('The reponse from XKCD was 200 OK.')
523 523
       end
524 524
     end
525 525
   end
@@ -549,8 +549,8 @@ fire: hot
549 549
 
550 550
     describe "#check" do
551 551
       it "should check for changes" do
552
-        lambda { @checker.check }.should change { Event.count }.by(1)
553
-        lambda { @checker.check }.should_not change { Event.count }
552
+        expect { @checker.check }.to change { Event.count }.by(1)
553
+        expect { @checker.check }.not_to change { Event.count }
554 554
       end
555 555
     end
556 556
   end
@@ -578,7 +578,7 @@ fire: hot
578 578
 
579 579
     describe "#check" do
580 580
       it "should check for changes" do
581
-        lambda { @checker.check }.should change { Event.count }.by(1)
581
+        expect { @checker.check }.to change { Event.count }.by(1)
582 582
       end
583 583
     end
584 584
   end

+ 9 - 9
spec/models/agents/weibo_publish_agent_spec.rb

@@ -38,8 +38,8 @@ describe Agents::WeiboPublishAgent do
38 38
       event2.save!
39 39
 
40 40
       Agents::WeiboPublishAgent.async_receive(@checker.id, [event1.id, event2.id])
41
-      @sent_messages.count.should eq(2)
42
-      @checker.events.count.should eq(2)
41
+      expect(@sent_messages.count).to eq(2)
42
+      expect(@checker.events.count).to eq(2)
43 43
     end
44 44
   end
45 45
 
@@ -51,20 +51,20 @@ describe Agents::WeiboPublishAgent do
51 51
       event.save!
52 52
 
53 53
       Agents::WeiboPublishAgent.async_receive(@checker.id, [event.id])
54
-      @sent_messages.count.should eq(1)
55
-      @checker.events.count.should eq(1)
56
-      @sent_messages.first.include?("t.co").should_not be_truthy
54
+      expect(@sent_messages.count).to eq(1)
55
+      expect(@checker.events.count).to eq(1)
56
+      expect(@sent_messages.first.include?("t.co")).not_to be_truthy
57 57
     end
58 58
   end
59 59
 
60 60
   describe '#working?' do
61 61
     it 'checks if events have been received within the expected receive period' do
62
-      @checker.should_not be_working # No events received
62
+      expect(@checker).not_to be_working # No events received
63 63
       Agents::WeiboPublishAgent.async_receive(@checker.id, [@event.id])
64
-      @checker.reload.should be_working # Just received events
64
+      expect(@checker.reload).to be_working # Just received events
65 65
       two_days_from_now = 2.days.from_now
66 66
       stub(Time).now { two_days_from_now }
67
-      @checker.reload.should_not be_working # More time has passed than the expected receive period without any new events
67
+      expect(@checker.reload).not_to be_working # More time has passed than the expected receive period without any new events
68 68
     end
69 69
   end
70
-end
70
+end

+ 2 - 2
spec/models/agents/weibo_user_agent_spec.rb

@@ -21,8 +21,8 @@ describe Agents::WeiboUserAgent do
21 21
 
22 22
   describe "#check" do
23 23
     it "should check for changes" do
24
-      lambda { @checker.check }.should change { Event.count }.by(1)
24
+      expect { @checker.check }.to change { Event.count }.by(1)
25 25
     end
26 26
   end
27 27
 
28
-end
28
+end

+ 3 - 3
spec/models/concerns/oauthable.rb

@@ -13,17 +13,17 @@ shared_examples_for Oauthable do
13 13
   end
14 14
 
15 15
   it "should be oauthable" do
16
-    @agent.oauthable?.should == true
16
+    expect(@agent.oauthable?).to eq(true)
17 17
   end
18 18
 
19 19
   describe "valid_services_for" do
20 20
     it "should return all available services without specifying valid_oauth_providers" do
21 21
       @agent = Agents::OauthableTestAgent.new
22
-      @agent.valid_services_for(users(:bob)).collect(&:id).sort.should == [services(:generic), services(:global)].collect(&:id).sort
22
+      expect(@agent.valid_services_for(users(:bob)).collect(&:id).sort).to eq([services(:generic), services(:global)].collect(&:id).sort)
23 23
     end
24 24
 
25 25
     it "should filter the services based on the agent defaults" do
26
-      @agent.valid_services_for(users(:bob)).to_a.should == Service.where(provider: @agent.valid_oauth_providers)
26
+      expect(@agent.valid_services_for(users(:bob)).to_a).to eq(Service.where(provider: @agent.valid_oauth_providers))
27 27
     end
28 28
   end
29 29
 end

+ 51 - 51
spec/models/event_spec.rb

@@ -7,23 +7,23 @@ describe Event do
7 7
       event.lat = 2
8 8
       event.lng = 3
9 9
       event.save!
10
-      Event.with_location.pluck(:id).should == [event.id]
10
+      expect(Event.with_location.pluck(:id)).to eq([event.id])
11 11
 
12 12
       event.lat = nil
13 13
       event.save!
14
-      Event.with_location.should be_empty
14
+      expect(Event.with_location).to be_empty
15 15
     end
16 16
   end
17 17
 
18 18
   describe "#location" do
19 19
     it "returns a default hash when an event does not have a location" do
20 20
       event = events(:bob_website_agent_event)
21
-      event.location.should == Location.new(
21
+      expect(event.location).to eq(Location.new(
22 22
         lat: nil,
23 23
         lng: nil,
24 24
         radius: 0.0,
25 25
         speed: nil,
26
-        course: nil)
26
+        course: nil))
27 27
     end
28 28
 
29 29
     it "returns a hash containing location information" do
@@ -36,12 +36,12 @@ describe Event do
36 36
         course: 90.0,
37 37
       }
38 38
       event.save!
39
-      event.location.should == Location.new(
39
+      expect(event.location).to eq(Location.new(
40 40
         lat: 2.0,
41 41
         lng: 3.0,
42 42
         radius: 0.0,
43 43
         speed: 0.5,
44
-        course: 90.0)
44
+        course: 90.0))
45 45
     end
46 46
   end
47 47
 
@@ -50,14 +50,14 @@ describe Event do
50 50
       events(:bob_website_agent_event).lat = 2
51 51
       events(:bob_website_agent_event).lng = 3
52 52
       events(:bob_website_agent_event).created_at = 2.weeks.ago
53
-      lambda {
53
+      expect {
54 54
         events(:bob_website_agent_event).reemit!
55
-      }.should change { Event.count }.by(1)
56
-      Event.last.payload.should == events(:bob_website_agent_event).payload
57
-      Event.last.agent.should == events(:bob_website_agent_event).agent
58
-      Event.last.lat.should == 2
59
-      Event.last.lng.should == 3
60
-      Event.last.created_at.to_i.should be_within(2).of(Time.now.to_i)
55
+      }.to change { Event.count }.by(1)
56
+      expect(Event.last.payload).to eq(events(:bob_website_agent_event).payload)
57
+      expect(Event.last.agent).to eq(events(:bob_website_agent_event).agent)
58
+      expect(Event.last.lat).to eq(2)
59
+      expect(Event.last.lng).to eq(3)
60
+      expect(Event.last.created_at.to_i).to be_within(2).of(Time.now.to_i)
61 61
     end
62 62
   end
63 63
 
@@ -76,31 +76,31 @@ describe Event do
76 76
       stub(Time).now { current_time }
77 77
 
78 78
       Event.cleanup_expired!
79
-      Event.find_by_id(half_hour_event.id).should_not be_nil
80
-      Event.find_by_id(one_hour_event.id).should_not be_nil
81
-      Event.find_by_id(two_hour_event.id).should_not be_nil
82
-      Event.find_by_id(three_hour_event.id).should_not be_nil
83
-      Event.find_by_id(non_expiring_event.id).should_not be_nil
84
-      agents(:bob_weather_agent).reload.events_count.should == initial_bob_count
85
-      agents(:jane_weather_agent).reload.events_count.should == initial_jane_count
79
+      expect(Event.find_by_id(half_hour_event.id)).not_to be_nil
80
+      expect(Event.find_by_id(one_hour_event.id)).not_to be_nil
81
+      expect(Event.find_by_id(two_hour_event.id)).not_to be_nil
82
+      expect(Event.find_by_id(three_hour_event.id)).not_to be_nil
83
+      expect(Event.find_by_id(non_expiring_event.id)).not_to be_nil
84
+      expect(agents(:bob_weather_agent).reload.events_count).to eq(initial_bob_count)
85
+      expect(agents(:jane_weather_agent).reload.events_count).to eq(initial_jane_count)
86 86
 
87 87
       current_time = 119.minutes.from_now # move almost 2 hours into the future
88 88
       Event.cleanup_expired!
89
-      Event.find_by_id(half_hour_event.id).should be_nil
90
-      Event.find_by_id(one_hour_event.id).should be_nil
91
-      Event.find_by_id(two_hour_event.id).should_not be_nil
92
-      Event.find_by_id(three_hour_event.id).should_not be_nil
93
-      Event.find_by_id(non_expiring_event.id).should_not be_nil
94
-      agents(:bob_weather_agent).reload.events_count.should == initial_bob_count - 1
95
-      agents(:jane_weather_agent).reload.events_count.should == initial_jane_count - 1
89
+      expect(Event.find_by_id(half_hour_event.id)).to be_nil
90
+      expect(Event.find_by_id(one_hour_event.id)).to be_nil
91
+      expect(Event.find_by_id(two_hour_event.id)).not_to be_nil
92
+      expect(Event.find_by_id(three_hour_event.id)).not_to be_nil
93
+      expect(Event.find_by_id(non_expiring_event.id)).not_to be_nil
94
+      expect(agents(:bob_weather_agent).reload.events_count).to eq(initial_bob_count - 1)
95
+      expect(agents(:jane_weather_agent).reload.events_count).to eq(initial_jane_count - 1)
96 96
 
97 97
       current_time = 2.minutes.from_now # move 2 minutes further into the future
98 98
       Event.cleanup_expired!
99
-      Event.find_by_id(two_hour_event.id).should be_nil
100
-      Event.find_by_id(three_hour_event.id).should_not be_nil
101
-      Event.find_by_id(non_expiring_event.id).should_not be_nil
102
-      agents(:bob_weather_agent).reload.events_count.should == initial_bob_count - 1
103
-      agents(:jane_weather_agent).reload.events_count.should == initial_jane_count - 2
99
+      expect(Event.find_by_id(two_hour_event.id)).to be_nil
100
+      expect(Event.find_by_id(three_hour_event.id)).not_to be_nil
101
+      expect(Event.find_by_id(non_expiring_event.id)).not_to be_nil
102
+      expect(agents(:bob_weather_agent).reload.events_count).to eq(initial_bob_count - 1)
103
+      expect(agents(:jane_weather_agent).reload.events_count).to eq(initial_jane_count - 2)
104 104
     end
105 105
 
106 106
     it "doesn't touch Events with no expired_at" do
@@ -113,37 +113,37 @@ describe Event do
113 113
       stub(Time).now { current_time }
114 114
 
115 115
       Event.cleanup_expired!
116
-      Event.find_by_id(event.id).should_not be_nil
116
+      expect(Event.find_by_id(event.id)).not_to be_nil
117 117
       current_time = 2.days.from_now
118 118
       Event.cleanup_expired!
119
-      Event.find_by_id(event.id).should_not be_nil
119
+      expect(Event.find_by_id(event.id)).not_to be_nil
120 120
     end
121 121
   end
122 122
 
123 123
   describe "after destroy" do
124 124
     it "nullifies any dependent AgentLogs" do
125
-      agent_logs(:log_for_jane_website_agent).outbound_event_id.should be_present
126
-      agent_logs(:log_for_bob_website_agent).outbound_event_id.should be_present
125
+      expect(agent_logs(:log_for_jane_website_agent).outbound_event_id).to be_present
126
+      expect(agent_logs(:log_for_bob_website_agent).outbound_event_id).to be_present
127 127
 
128 128
       agent_logs(:log_for_bob_website_agent).outbound_event.destroy
129 129
 
130
-      agent_logs(:log_for_jane_website_agent).reload.outbound_event_id.should be_present
131
-      agent_logs(:log_for_bob_website_agent).reload.outbound_event_id.should be_nil
130
+      expect(agent_logs(:log_for_jane_website_agent).reload.outbound_event_id).to be_present
131
+      expect(agent_logs(:log_for_bob_website_agent).reload.outbound_event_id).to be_nil
132 132
     end
133 133
   end
134 134
 
135 135
   describe "caches" do
136 136
     describe "when an event is created" do
137 137
       it "updates a counter cache on agent" do
138
-        lambda {
138
+        expect {
139 139
           agents(:jane_weather_agent).events.create!(:user => users(:jane))
140
-        }.should change { agents(:jane_weather_agent).reload.events_count }.by(1)
140
+        }.to change { agents(:jane_weather_agent).reload.events_count }.by(1)
141 141
       end
142 142
 
143 143
       it "updates last_event_at on agent" do
144
-        lambda {
144
+        expect {
145 145
           agents(:jane_weather_agent).events.create!(:user => users(:jane))
146
-        }.should change { agents(:jane_weather_agent).reload.last_event_at }
146
+        }.to change { agents(:jane_weather_agent).reload.last_event_at }
147 147
       end
148 148
     end
149 149
 
@@ -153,9 +153,9 @@ describe Event do
153 153
 
154 154
         agents(:jane_weather_agent).update_attribute :last_event_at, 2.days.ago
155 155
 
156
-        lambda {
156
+        expect {
157 157
           event.update_attribute :payload, { 'hello' => 'world' }
158
-        }.should_not change { agents(:jane_weather_agent).reload.last_event_at }
158
+        }.not_to change { agents(:jane_weather_agent).reload.last_event_at }
159 159
       end
160 160
     end
161 161
   end
@@ -180,12 +180,12 @@ describe EventDrop do
180 180
   end
181 181
 
182 182
   it 'should be created via Agent#to_liquid' do
183
-    @event.to_liquid.class.should be(EventDrop)
183
+    expect(@event.to_liquid.class).to be(EventDrop)
184 184
   end
185 185
 
186 186
   it 'should have attributes of its payload' do
187 187
     t = '{{title}}: {{url}}'
188
-    interpolate(t, @event).should eq('some title: http://some.site.example.org/')
188
+    expect(interpolate(t, @event)).to eq('some title: http://some.site.example.org/')
189 189
   end
190 190
 
191 191
   it 'should use created_at from the payload if it exists' do
@@ -194,27 +194,27 @@ describe EventDrop do
194 194
     @event.payload['created_at'] = created_at.strftime("%s")
195 195
     @event.save!
196 196
     t = '{{created_at | date:"%s" }}'
197
-    interpolate(t, @event).should eq(created_at.strftime("%s"))
197
+    expect(interpolate(t, @event)).to eq(created_at.strftime("%s"))
198 198
   end
199 199
 
200 200
   it 'should be iteratable' do
201 201
     # to_liquid returns self
202 202
     t = "{% for pair in to_liquid %}{{pair | join:':' }}\n{% endfor %}"
203
-    interpolate(t, @event).should eq("title:some title\nurl:http://some.site.example.org/\n")
203
+    expect(interpolate(t, @event)).to eq("title:some title\nurl:http://some.site.example.org/\n")
204 204
   end
205 205
 
206 206
   it 'should have agent' do
207 207
     t = '{{agent.name}}'
208
-    interpolate(t, @event).should eq('SF Weather')
208
+    expect(interpolate(t, @event)).to eq('SF Weather')
209 209
   end
210 210
 
211 211
   it 'should have created_at' do
212 212
     t = '{{created_at | date:"%FT%T%z" }}'
213
-    interpolate(t, @event).should eq(@event.created_at.strftime("%FT%T%z"))
213
+    expect(interpolate(t, @event)).to eq(@event.created_at.strftime("%FT%T%z"))
214 214
   end
215 215
 
216 216
   it 'should have _location_' do
217 217
     t = '{{_location_.lat}},{{_location_.lng}}'
218
-    interpolate(t, @event).should eq("2.0,3.0")
218
+    expect(interpolate(t, @event)).to eq("2.0,3.0")
219 219
   end
220 220
 end

+ 127 - 127
spec/models/scenario_import_spec.rb

@@ -82,7 +82,7 @@ describe ScenarioImport do
82 82
 
83 83
   describe "initialization" do
84 84
     it "is initialized with an attributes hash" do
85
-      ScenarioImport.new(:url => "http://google.com").url.should == "http://google.com"
85
+      expect(ScenarioImport.new(:url => "http://google.com").url).to eq("http://google.com")
86 86
     end
87 87
   end
88 88
 
@@ -94,79 +94,79 @@ describe ScenarioImport do
94 94
     end
95 95
 
96 96
     it "is not valid when none of file, url, or data are present" do
97
-      subject.should_not be_valid
98
-      subject.should have(1).error_on(:base)
99
-      subject.errors[:base].should include("Please provide either a Scenario JSON File or a Public Scenario URL.")
97
+      expect(subject).not_to be_valid
98
+      expect(subject).to have(1).error_on(:base)
99
+      expect(subject.errors[:base]).to include("Please provide either a Scenario JSON File or a Public Scenario URL.")
100 100
     end
101 101
 
102 102
     describe "data" do
103 103
       it "should be invalid with invalid data" do
104 104
         subject.data = invalid_data
105
-        subject.should_not be_valid
106
-        subject.should have(1).error_on(:base)
105
+        expect(subject).not_to be_valid
106
+        expect(subject).to have(1).error_on(:base)
107 107
 
108 108
         subject.data = "foo"
109
-        subject.should_not be_valid
110
-        subject.should have(1).error_on(:base)
109
+        expect(subject).not_to be_valid
110
+        expect(subject).to have(1).error_on(:base)
111 111
 
112 112
         # It also clears the data when invalid
113
-        subject.data.should be_nil
113
+        expect(subject.data).to be_nil
114 114
       end
115 115
 
116 116
       it "should be valid with valid data" do
117 117
         subject.data = valid_data
118
-        subject.should be_valid
118
+        expect(subject).to be_valid
119 119
       end
120 120
     end
121 121
 
122 122
     describe "url" do
123 123
       it "should be invalid with an unreasonable URL" do
124 124
         subject.url = "foo"
125
-        subject.should_not be_valid
126
-        subject.should have(1).error_on(:url)
127
-        subject.errors[:url].should include("appears to be invalid")
125
+        expect(subject).not_to be_valid
126
+        expect(subject).to have(1).error_on(:url)
127
+        expect(subject.errors[:url]).to include("appears to be invalid")
128 128
       end
129 129
 
130 130
       it "should be invalid when the referenced url doesn't contain a scenario" do
131 131
         stub_request(:get, "http://example.com/scenarios/1/export.json").to_return(:status => 200, :body => invalid_data)
132 132
         subject.url = "http://example.com/scenarios/1/export.json"
133
-        subject.should_not be_valid
134
-        subject.errors[:base].should include("The provided data does not appear to be a valid Scenario.")
133
+        expect(subject).not_to be_valid
134
+        expect(subject.errors[:base]).to include("The provided data does not appear to be a valid Scenario.")
135 135
       end
136 136
 
137 137
       it "should be valid when the url points to a valid scenario" do
138 138
         stub_request(:get, "http://example.com/scenarios/1/export.json").to_return(:status => 200, :body => valid_data)
139 139
         subject.url = "http://example.com/scenarios/1/export.json"
140
-        subject.should be_valid
140
+        expect(subject).to be_valid
141 141
       end
142 142
     end
143 143
 
144 144
     describe "file" do
145 145
       it "should be invalid when the uploaded file doesn't contain a scenario" do
146 146
         subject.file = StringIO.new("foo")
147
-        subject.should_not be_valid
148
-        subject.errors[:base].should include("The provided data does not appear to be a valid Scenario.")
147
+        expect(subject).not_to be_valid
148
+        expect(subject.errors[:base]).to include("The provided data does not appear to be a valid Scenario.")
149 149
 
150 150
         subject.file = StringIO.new(invalid_data)
151
-        subject.should_not be_valid
152
-        subject.errors[:base].should include("The provided data does not appear to be a valid Scenario.")
151
+        expect(subject).not_to be_valid
152
+        expect(subject.errors[:base]).to include("The provided data does not appear to be a valid Scenario.")
153 153
       end
154 154
 
155 155
       it "should be valid with a valid uploaded scenario" do
156 156
         subject.file = StringIO.new(valid_data)
157
-        subject.should be_valid
157
+        expect(subject).to be_valid
158 158
       end
159 159
     end
160 160
   end
161 161
 
162 162
   describe "#dangerous?" do
163 163
     it "returns false on most Agents" do
164
-      ScenarioImport.new(:data => valid_data).should_not be_dangerous
164
+      expect(ScenarioImport.new(:data => valid_data)).not_to be_dangerous
165 165
     end
166 166
 
167 167
     it "returns true if a ShellCommandAgent is present" do
168 168
       valid_parsed_data[:agents][0][:type] = "Agents::ShellCommandAgent"
169
-      ScenarioImport.new(:data => valid_parsed_data.to_json).should be_dangerous
169
+      expect(ScenarioImport.new(:data => valid_parsed_data.to_json)).to be_dangerous
170 170
     end
171 171
   end
172 172
 
@@ -180,57 +180,57 @@ describe ScenarioImport do
180 180
     context "when this scenario has never been seen before" do
181 181
       describe "#import" do
182 182
         it "makes a new scenario" do
183
-          lambda {
183
+          expect {
184 184
             scenario_import.import(:skip_agents => true)
185
-          }.should change { users(:bob).scenarios.count }.by(1)
186
-
187
-          scenario_import.scenario.name.should == name
188
-          scenario_import.scenario.description.should == description
189
-          scenario_import.scenario.guid.should == guid
190
-          scenario_import.scenario.tag_fg_color.should == tag_fg_color
191
-          scenario_import.scenario.tag_bg_color.should == tag_bg_color
192
-          scenario_import.scenario.source_url.should == source_url
193
-          scenario_import.scenario.public.should be_falsey
185
+          }.to change { users(:bob).scenarios.count }.by(1)
186
+
187
+          expect(scenario_import.scenario.name).to eq(name)
188
+          expect(scenario_import.scenario.description).to eq(description)
189
+          expect(scenario_import.scenario.guid).to eq(guid)
190
+          expect(scenario_import.scenario.tag_fg_color).to eq(tag_fg_color)
191
+          expect(scenario_import.scenario.tag_bg_color).to eq(tag_bg_color)
192
+          expect(scenario_import.scenario.source_url).to eq(source_url)
193
+          expect(scenario_import.scenario.public).to be_falsey
194 194
         end
195 195
 
196 196
         it "creates the Agents" do
197
-          lambda {
197
+          expect {
198 198
             scenario_import.import
199
-          }.should change { users(:bob).agents.count }.by(2)
199
+          }.to change { users(:bob).agents.count }.by(2)
200 200
 
201 201
           weather_agent = scenario_import.scenario.agents.find_by(:guid => "a-weather-agent")
202 202
           trigger_agent = scenario_import.scenario.agents.find_by(:guid => "a-trigger-agent")
203 203
 
204
-          weather_agent.name.should == "a weather agent"
205
-          weather_agent.schedule.should == "5pm"
206
-          weather_agent.keep_events_for.should == 14
207
-          weather_agent.propagate_immediately.should be_falsey
208
-          weather_agent.should be_disabled
209
-          weather_agent.memory.should be_empty
210
-          weather_agent.options.should == weather_agent_options
211
-
212
-          trigger_agent.name.should == "listen for weather"
213
-          trigger_agent.sources.should == [weather_agent]
214
-          trigger_agent.schedule.should be_nil
215
-          trigger_agent.keep_events_for.should == 0
216
-          trigger_agent.propagate_immediately.should be_truthy
217
-          trigger_agent.should_not be_disabled
218
-          trigger_agent.memory.should be_empty
219
-          trigger_agent.options.should == trigger_agent_options
204
+          expect(weather_agent.name).to eq("a weather agent")
205
+          expect(weather_agent.schedule).to eq("5pm")
206
+          expect(weather_agent.keep_events_for).to eq(14)
207
+          expect(weather_agent.propagate_immediately).to be_falsey
208
+          expect(weather_agent).to be_disabled
209
+          expect(weather_agent.memory).to be_empty
210
+          expect(weather_agent.options).to eq(weather_agent_options)
211
+
212
+          expect(trigger_agent.name).to eq("listen for weather")
213
+          expect(trigger_agent.sources).to eq([weather_agent])
214
+          expect(trigger_agent.schedule).to be_nil
215
+          expect(trigger_agent.keep_events_for).to eq(0)
216
+          expect(trigger_agent.propagate_immediately).to be_truthy
217
+          expect(trigger_agent).not_to be_disabled
218
+          expect(trigger_agent.memory).to be_empty
219
+          expect(trigger_agent.options).to eq(trigger_agent_options)
220 220
         end
221 221
 
222 222
         it "creates new Agents, even if one already exists with the given guid (so that we don't overwrite a user's work outside of the scenario)" do
223 223
           agents(:bob_weather_agent).update_attribute :guid, "a-weather-agent"
224 224
 
225
-          lambda {
225
+          expect {
226 226
             scenario_import.import
227
-          }.should change { users(:bob).agents.count }.by(2)
227
+          }.to change { users(:bob).agents.count }.by(2)
228 228
         end
229 229
       end
230 230
 
231 231
       describe "#generate_diff" do
232 232
         it "returns AgentDiff objects for the incoming Agents" do
233
-          scenario_import.should be_valid
233
+          expect(scenario_import).to be_valid
234 234
 
235 235
           agent_diffs = scenario_import.agent_diffs
236 236
 
@@ -241,27 +241,27 @@ describe ScenarioImport do
241 241
             if key == :type
242 242
               value = value.split("::").last
243 243
             end
244
-            weather_agent_diff.should respond_to(key)
244
+            expect(weather_agent_diff).to respond_to(key)
245 245
             field = weather_agent_diff.send(key)
246
-            field.should be_a(ScenarioImport::AgentDiff::FieldDiff)
247
-            field.incoming.should == value
248
-            field.updated.should == value
249
-            field.current.should be_nil
246
+            expect(field).to be_a(ScenarioImport::AgentDiff::FieldDiff)
247
+            expect(field.incoming).to eq(value)
248
+            expect(field.updated).to eq(value)
249
+            expect(field.current).to be_nil
250 250
           end
251
-          weather_agent_diff.should_not respond_to(:propagate_immediately)
251
+          expect(weather_agent_diff).not_to respond_to(:propagate_immediately)
252 252
 
253 253
           valid_parsed_trigger_agent_data.each do |key, value|
254 254
             if key == :type
255 255
               value = value.split("::").last
256 256
             end
257
-            trigger_agent_diff.should respond_to(key)
257
+            expect(trigger_agent_diff).to respond_to(key)
258 258
             field = trigger_agent_diff.send(key)
259
-            field.should be_a(ScenarioImport::AgentDiff::FieldDiff)
260
-            field.incoming.should == value
261
-            field.updated.should == value
262
-            field.current.should be_nil
259
+            expect(field).to be_a(ScenarioImport::AgentDiff::FieldDiff)
260
+            expect(field.incoming).to eq(value)
261
+            expect(field.updated).to eq(value)
262
+            expect(field.current).to be_nil
263 263
           end
264
-          trigger_agent_diff.should_not respond_to(:schedule)
264
+          expect(trigger_agent_diff).not_to respond_to(:schedule)
265 265
         end
266 266
       end
267 267
     end
@@ -280,49 +280,49 @@ describe ScenarioImport do
280 280
 
281 281
       describe "#import" do
282 282
         it "uses the existing scenario, updating its data" do
283
-          lambda {
283
+          expect {
284 284
             scenario_import.import(:skip_agents => true)
285
-            scenario_import.scenario.should == existing_scenario
286
-          }.should_not change { users(:bob).scenarios.count }
285
+            expect(scenario_import.scenario).to eq(existing_scenario)
286
+          }.not_to change { users(:bob).scenarios.count }
287 287
 
288 288
           existing_scenario.reload
289
-          existing_scenario.guid.should == guid
290
-          existing_scenario.tag_fg_color.should == tag_fg_color
291
-          existing_scenario.tag_bg_color.should == tag_bg_color
292
-          existing_scenario.description.should == description
293
-          existing_scenario.name.should == name
294
-          existing_scenario.source_url.should == source_url
295
-          existing_scenario.public.should be_falsey
289
+          expect(existing_scenario.guid).to eq(guid)
290
+          expect(existing_scenario.tag_fg_color).to eq(tag_fg_color)
291
+          expect(existing_scenario.tag_bg_color).to eq(tag_bg_color)
292
+          expect(existing_scenario.description).to eq(description)
293
+          expect(existing_scenario.name).to eq(name)
294
+          expect(existing_scenario.source_url).to eq(source_url)
295
+          expect(existing_scenario.public).to be_falsey
296 296
         end
297 297
 
298 298
         it "updates any existing agents in the scenario, and makes new ones as needed" do
299
-          scenario_import.should be_valid
299
+          expect(scenario_import).to be_valid
300 300
 
301
-          lambda {
301
+          expect {
302 302
             scenario_import.import
303
-          }.should change { users(:bob).agents.count }.by(1) # One, because the weather agent already existed.
303
+          }.to change { users(:bob).agents.count }.by(1) # One, because the weather agent already existed.
304 304
 
305 305
           weather_agent = existing_scenario.agents.find_by(:guid => "a-weather-agent")
306 306
           trigger_agent = existing_scenario.agents.find_by(:guid => "a-trigger-agent")
307 307
 
308
-          weather_agent.should == agents(:bob_weather_agent)
309
-
310
-          weather_agent.name.should == "a weather agent"
311
-          weather_agent.schedule.should == "5pm"
312
-          weather_agent.keep_events_for.should == 14
313
-          weather_agent.propagate_immediately.should be_falsey
314
-          weather_agent.should be_disabled
315
-          weather_agent.memory.should be_empty
316
-          weather_agent.options.should == weather_agent_options
317
-
318
-          trigger_agent.name.should == "listen for weather"
319
-          trigger_agent.sources.should == [weather_agent]
320
-          trigger_agent.schedule.should be_nil
321
-          trigger_agent.keep_events_for.should == 0
322
-          trigger_agent.propagate_immediately.should be_truthy
323
-          trigger_agent.should_not be_disabled
324
-          trigger_agent.memory.should be_empty
325
-          trigger_agent.options.should == trigger_agent_options
308
+          expect(weather_agent).to eq(agents(:bob_weather_agent))
309
+
310
+          expect(weather_agent.name).to eq("a weather agent")
311
+          expect(weather_agent.schedule).to eq("5pm")
312
+          expect(weather_agent.keep_events_for).to eq(14)
313
+          expect(weather_agent.propagate_immediately).to be_falsey
314
+          expect(weather_agent).to be_disabled
315
+          expect(weather_agent.memory).to be_empty
316
+          expect(weather_agent.options).to eq(weather_agent_options)
317
+
318
+          expect(trigger_agent.name).to eq("listen for weather")
319
+          expect(trigger_agent.sources).to eq([weather_agent])
320
+          expect(trigger_agent.schedule).to be_nil
321
+          expect(trigger_agent.keep_events_for).to eq(0)
322
+          expect(trigger_agent.propagate_immediately).to be_truthy
323
+          expect(trigger_agent).not_to be_disabled
324
+          expect(trigger_agent.memory).to be_empty
325
+          expect(trigger_agent.options).to eq(trigger_agent_options)
326 326
         end
327 327
 
328 328
         it "honors updates coming from the UI" do
@@ -336,16 +336,16 @@ describe ScenarioImport do
336 336
             }
337 337
           }
338 338
 
339
-          scenario_import.should be_valid
339
+          expect(scenario_import).to be_valid
340 340
 
341
-          scenario_import.import.should be_truthy
341
+          expect(scenario_import.import).to be_truthy
342 342
 
343 343
           weather_agent = existing_scenario.agents.find_by(:guid => "a-weather-agent")
344
-          weather_agent.name.should == "updated name"
345
-          weather_agent.schedule.should == "6pm"
346
-          weather_agent.keep_events_for.should == 2
347
-          weather_agent.should_not be_disabled
348
-          weather_agent.options.should == weather_agent_options.merge("api_key" => "foo")
344
+          expect(weather_agent.name).to eq("updated name")
345
+          expect(weather_agent.schedule).to eq("6pm")
346
+          expect(weather_agent.keep_events_for).to eq(2)
347
+          expect(weather_agent).not_to be_disabled
348
+          expect(weather_agent.options).to eq(weather_agent_options.merge("api_key" => "foo"))
349 349
         end
350 350
 
351 351
         it "adds errors when updated agents are invalid" do
@@ -358,12 +358,12 @@ describe ScenarioImport do
358 358
             }
359 359
           }
360 360
 
361
-          scenario_import.import.should be_falsey
361
+          expect(scenario_import.import).to be_falsey
362 362
 
363 363
           errors = scenario_import.errors.full_messages.to_sentence
364
-          errors.should =~ /Name can't be blank/
365
-          errors.should =~ /api_key is required/
366
-          errors.should =~ /Schedule is not a valid schedule/
364
+          expect(errors).to match(/Name can't be blank/)
365
+          expect(errors).to match(/api_key is required/)
366
+          expect(errors).to match(/Schedule is not a valid schedule/)
367 367
         end
368 368
       end
369 369
 
@@ -374,15 +374,15 @@ describe ScenarioImport do
374 374
           trigger_agent_diff = agent_diffs[1]
375 375
 
376 376
           # Already exists
377
-          weather_agent_diff.agent.should == agents(:bob_weather_agent)
377
+          expect(weather_agent_diff.agent).to eq(agents(:bob_weather_agent))
378 378
           valid_parsed_weather_agent_data.each do |key, value|
379 379
             next if key == :type
380
-            weather_agent_diff.send(key).current.should == agents(:bob_weather_agent).send(key)
380
+            expect(weather_agent_diff.send(key).current).to eq(agents(:bob_weather_agent).send(key))
381 381
           end
382 382
 
383 383
           # Doesn't exist yet
384 384
           valid_parsed_trigger_agent_data.each do |key, value|
385
-            trigger_agent_diff.send(key).current.should be_nil
385
+            expect(trigger_agent_diff.send(key).current).to be_nil
386 386
           end
387 387
         end
388 388
 
@@ -400,20 +400,20 @@ describe ScenarioImport do
400 400
             }
401 401
           }
402 402
 
403
-          scenario_import.should be_valid
403
+          expect(scenario_import).to be_valid
404 404
 
405 405
           agent_diffs = scenario_import.agent_diffs
406 406
           weather_agent_diff = agent_diffs[0]
407 407
           trigger_agent_diff = agent_diffs[1]
408 408
 
409
-          weather_agent_diff.name.current.should == agents(:bob_weather_agent).name
410
-          weather_agent_diff.name.incoming.should == valid_parsed_weather_agent_data[:name]
411
-          weather_agent_diff.name.updated.should == "a new name"
409
+          expect(weather_agent_diff.name.current).to eq(agents(:bob_weather_agent).name)
410
+          expect(weather_agent_diff.name.incoming).to eq(valid_parsed_weather_agent_data[:name])
411
+          expect(weather_agent_diff.name.updated).to eq("a new name")
412 412
 
413
-          weather_agent_diff.schedule.updated.should == "6pm"
414
-          weather_agent_diff.keep_events_for.updated.should == "2"
415
-          weather_agent_diff.disabled.updated.should == "true"
416
-          weather_agent_diff.options.updated.should == weather_agent_options.merge("api_key" => "foo")
413
+          expect(weather_agent_diff.schedule.updated).to eq("6pm")
414
+          expect(weather_agent_diff.keep_events_for.updated).to eq("2")
415
+          expect(weather_agent_diff.disabled.updated).to eq("true")
416
+          expect(weather_agent_diff.options.updated).to eq(weather_agent_options.merge("api_key" => "foo"))
417 417
         end
418 418
 
419 419
         it "adds errors on validation when updated options are unparsable" do
@@ -422,8 +422,8 @@ describe ScenarioImport do
422 422
               "options" => '{'
423 423
             }
424 424
           }
425
-          scenario_import.should_not be_valid
426
-          scenario_import.should have(1).error_on(:base)
425
+          expect(scenario_import).not_to be_valid
426
+          expect(scenario_import).to have(1).error_on(:base)
427 427
         end
428 428
       end
429 429
     end
@@ -448,12 +448,12 @@ describe ScenarioImport do
448 448
         it "should check if the agent requires a service" do
449 449
           agent_diffs = services_scenario_import.agent_diffs
450 450
           basecamp_agent_diff = agent_diffs[0]
451
-          basecamp_agent_diff.requires_service?.should == true
451
+          expect(basecamp_agent_diff.requires_service?).to eq(true)
452 452
         end
453 453
 
454 454
         it "should add an error when no service is selected" do
455
-          services_scenario_import.import.should == false
456
-          services_scenario_import.errors[:base].length.should == 1
455
+          expect(services_scenario_import.import).to eq(false)
456
+          expect(services_scenario_import.errors[:base].length).to eq(1)
457 457
         end
458 458
       end
459 459
 
@@ -464,9 +464,9 @@ describe ScenarioImport do
464 464
               "service_id" => "0",
465 465
             }
466 466
           }
467
-          lambda {
468
-            services_scenario_import.import.should == true
469
-          }.should change { users(:bob).agents.count }.by(2)
467
+          expect {
468
+            expect(services_scenario_import.import).to eq(true)
469
+          }.to change { users(:bob).agents.count }.by(2)
470 470
         end
471 471
       end
472 472
     end

+ 15 - 15
spec/models/scenario_spec.rb

@@ -7,61 +7,61 @@ describe Scenario do
7 7
 
8 8
   describe "validations" do
9 9
     before do
10
-      new_instance.should be_valid
10
+      expect(new_instance).to be_valid
11 11
     end
12 12
 
13 13
     it "validates the presence of name" do
14 14
       new_instance.name = ''
15
-      new_instance.should_not be_valid
15
+      expect(new_instance).not_to be_valid
16 16
     end
17 17
 
18 18
     it "validates the presence of user" do
19 19
       new_instance.user = nil
20
-      new_instance.should_not be_valid
20
+      expect(new_instance).not_to be_valid
21 21
     end
22 22
 
23 23
     it "validates tag_fg_color is hex color" do
24 24
       new_instance.tag_fg_color = '#N07H3X'
25
-      new_instance.should_not be_valid
25
+      expect(new_instance).not_to be_valid
26 26
       new_instance.tag_fg_color = '#BADA55'
27
-      new_instance.should be_valid
27
+      expect(new_instance).to be_valid
28 28
     end
29 29
 
30 30
     it "allows nil tag_fg_color" do
31 31
       new_instance.tag_fg_color = nil
32
-      new_instance.should be_valid
32
+      expect(new_instance).to be_valid
33 33
     end
34 34
 
35 35
     it "validates tag_bg_color is hex color" do
36 36
       new_instance.tag_bg_color = '#N07H3X'
37
-      new_instance.should_not be_valid
37
+      expect(new_instance).not_to be_valid
38 38
       new_instance.tag_bg_color = '#BADA55'
39
-      new_instance.should be_valid
39
+      expect(new_instance).to be_valid
40 40
     end
41 41
 
42 42
     it "allows nil tag_bg_color" do
43 43
       new_instance.tag_bg_color = nil
44
-      new_instance.should be_valid
44
+      expect(new_instance).to be_valid
45 45
     end
46 46
 
47 47
     it "only allows Agents owned by user" do
48 48
       new_instance.agent_ids = [agents(:bob_website_agent).id]
49
-      new_instance.should be_valid
49
+      expect(new_instance).to be_valid
50 50
 
51 51
       new_instance.agent_ids = [agents(:jane_website_agent).id]
52
-      new_instance.should_not be_valid
52
+      expect(new_instance).not_to be_valid
53 53
     end
54 54
   end
55 55
 
56 56
   describe "counters" do
57 57
     it "maintains a counter cache on user" do
58
-      lambda {
58
+      expect {
59 59
         new_instance.save!
60
-      }.should change { users(:bob).reload.scenario_count }.by(1)
60
+      }.to change { users(:bob).reload.scenario_count }.by(1)
61 61
 
62
-      lambda {
62
+      expect {
63 63
         new_instance.destroy
64
-      }.should change { users(:bob).reload.scenario_count }.by(-1)
64
+      }.to change { users(:bob).reload.scenario_count }.by(-1)
65 65
     end
66 66
   end
67 67
 end

+ 29 - 29
spec/models/service_spec.rb

@@ -8,11 +8,11 @@ describe Service do
8 8
   describe "#toggle_availability!" do
9 9
     it "should toggle the global flag" do
10 10
       @service = services(:generic)
11
-      @service.global.should == false
11
+      expect(@service.global).to eq(false)
12 12
       @service.toggle_availability!
13
-      @service.global.should == true
13
+      expect(@service.global).to eq(true)
14 14
       @service.toggle_availability!
15
-      @service.global.should == false
15
+      expect(@service.global).to eq(false)
16 16
     end
17 17
 
18 18
     it "disconnects agents and disables them if the previously global service is made private again", focus: true do
@@ -21,15 +21,15 @@ describe Service do
21 21
 
22 22
       service = agent.service
23 23
       service.toggle_availability!
24
-      service.agents.length.should == 2
24
+      expect(service.agents.length).to eq(2)
25 25
 
26 26
       service.toggle_availability!
27 27
       jane_agent.reload
28
-      jane_agent.service_id.should be_nil
29
-      jane_agent.disabled.should be true
28
+      expect(jane_agent.service_id).to be_nil
29
+      expect(jane_agent.disabled).to be true
30 30
 
31 31
       service.reload
32
-      service.agents.length.should == 1
32
+      expect(service.agents.length).to eq(1)
33 33
     end
34 34
   end
35 35
 
@@ -38,8 +38,8 @@ describe Service do
38 38
     service = agent.service
39 39
     service.destroy
40 40
     agent.reload
41
-    agent.service_id.should be_nil
42
-    agent.disabled.should be true
41
+    expect(agent.service_id).to be_nil
42
+    expect(agent.disabled).to be true
43 43
   end
44 44
 
45 45
   describe "preparing for a request" do
@@ -49,18 +49,18 @@ describe Service do
49 49
 
50 50
     it "should not update the token if the token never expires" do
51 51
       @service.expires_at = nil
52
-      @service.prepare_request.should == nil
52
+      expect(@service.prepare_request).to eq(nil)
53 53
     end
54 54
 
55 55
     it "should not update the token if the token is still valid" do
56 56
       @service.expires_at = Time.now + 1.hour
57
-      @service.prepare_request.should == nil
57
+      expect(@service.prepare_request).to eq(nil)
58 58
     end
59 59
 
60 60
     it "should call refresh_token! if the token expired" do
61 61
       stub(@service).refresh_token! { @service }
62 62
       @service.expires_at = Time.now - 1.hour
63
-      @service.prepare_request.should == @service
63
+      expect(@service.prepare_request).to eq(@service)
64 64
     end
65 65
   end
66 66
 
@@ -71,7 +71,7 @@ describe Service do
71 71
 
72 72
     it "should return the correct endpoint" do
73 73
       @service.provider = '37signals'
74
-      @service.send(:endpoint).to_s.should == "https://launchpad.37signals.com/authorization/token"
74
+      expect(@service.send(:endpoint).to_s).to eq("https://launchpad.37signals.com/authorization/token")
75 75
     end
76 76
 
77 77
     it "should update the token" do
@@ -80,7 +80,7 @@ describe Service do
80 80
       @service.provider = '37signals'
81 81
       @service.refresh_token = 'refreshtokentest'
82 82
       @service.refresh_token!
83
-      @service.token.should == 'NEWTOKEN'
83
+      expect(@service.token).to eq('NEWTOKEN')
84 84
     end
85 85
   end
86 86
 
@@ -92,11 +92,11 @@ describe Service do
92 92
         service.save!
93 93
       }.to change { @user.services.count }.by(1)
94 94
       service = @user.services.first
95
-      service.name.should == 'johnqpublic'
96
-      service.uid.should == '123456'
97
-      service.provider.should == 'twitter'
98
-      service.token.should == 'a1b2c3d4...'
99
-      service.secret.should == 'abcdef1234'
95
+      expect(service.name).to eq('johnqpublic')
96
+      expect(service.uid).to eq('123456')
97
+      expect(service.provider).to eq('twitter')
98
+      expect(service.token).to eq('a1b2c3d4...')
99
+      expect(service.secret).to eq('abcdef1234')
100 100
     end
101 101
     it "should work with 37signals services" do
102 102
       signals = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/37signals.json')))
@@ -105,12 +105,12 @@ describe Service do
105 105
         service.save!
106 106
       }.to change { @user.services.count }.by(1)
107 107
       service = @user.services.first
108
-      service.provider.should == '37signals'
109
-      service.name.should == 'Dominik Sander'
110
-      service.token.should == 'abcde'
111
-      service.uid.should == '12345'
112
-      service.refresh_token.should == 'fghrefresh'
113
-      service.options[:user_id].should == 12345
108
+      expect(service.provider).to eq('37signals')
109
+      expect(service.name).to eq('Dominik Sander')
110
+      expect(service.token).to eq('abcde')
111
+      expect(service.uid).to eq('12345')
112
+      expect(service.refresh_token).to eq('fghrefresh')
113
+      expect(service.options[:user_id]).to eq(12345)
114 114
       service.expires_at = Time.at(1401554352)
115 115
     end
116 116
     it "should work with github services" do
@@ -120,10 +120,10 @@ describe Service do
120 120
         service.save!
121 121
       }.to change { @user.services.count }.by(1)
122 122
       service = @user.services.first
123
-      service.provider.should == 'github'
124
-      service.name.should == 'dsander'
125
-      service.uid.should == '12345'
126
-      service.token.should == 'agithubtoken'
123
+      expect(service.provider).to eq('github')
124
+      expect(service.name).to eq('dsander')
125
+      expect(service.uid).to eq('12345')
126
+      expect(service.token).to eq('agithubtoken')
127 127
     end
128 128
   end
129 129
 end

+ 9 - 9
spec/models/user_credential_spec.rb

@@ -2,18 +2,18 @@ require 'spec_helper'
2 2
 
3 3
 describe UserCredential do
4 4
   describe "validation" do
5
-    it { should validate_uniqueness_of(:credential_name).scoped_to(:user_id) }
6
-    it { should validate_presence_of(:credential_name) }
7
-    it { should validate_presence_of(:credential_value) }
8
-    it { should validate_presence_of(:user_id) }
5
+    it { is_expected.to validate_uniqueness_of(:credential_name).scoped_to(:user_id) }
6
+    it { is_expected.to validate_presence_of(:credential_name) }
7
+    it { is_expected.to validate_presence_of(:credential_value) }
8
+    it { is_expected.to validate_presence_of(:user_id) }
9 9
   end
10 10
 
11 11
   describe "mass assignment" do
12
-    it { should allow_mass_assignment_of :credential_name }
12
+    it { is_expected.to allow_mass_assignment_of :credential_name }
13 13
 
14
-    it { should allow_mass_assignment_of :credential_value }
14
+    it { is_expected.to allow_mass_assignment_of :credential_value }
15 15
 
16
-    it { should_not allow_mass_assignment_of :user_id }
16
+    it { is_expected.not_to allow_mass_assignment_of :user_id }
17 17
   end
18 18
 
19 19
   describe "cleaning fields" do
@@ -22,8 +22,8 @@ describe UserCredential do
22 22
       user_credential.credential_name = " new name "
23 23
       user_credential.credential_value = " new value "
24 24
       user_credential.save!
25
-      user_credential.credential_name.should == "new name"
26
-      user_credential.credential_value.should == "new value"
25
+      expect(user_credential.credential_name).to eq("new name")
26
+      expect(user_credential.credential_value).to eq("new value")
27 27
     end
28 28
   end
29 29
 end

+ 2 - 2
spec/models/users_spec.rb

@@ -5,13 +5,13 @@ describe User do
5 5
     describe "invitation_code" do
6 6
       it "only accepts valid invitation codes" do
7 7
         User::INVITATION_CODES.each do |v|
8
-          should allow_value(v).for(:invitation_code)
8
+          is_expected.to allow_value(v).for(:invitation_code)
9 9
         end
10 10
       end
11 11
 
12 12
       it "can reject invalid invitation codes" do
13 13
         %w['foo', 'bar'].each do |v|
14
-          should_not allow_value(v).for(:invitation_code)
14
+          is_expected.not_to allow_value(v).for(:invitation_code)
15 15
         end
16 16
       end
17 17
     end

+ 8 - 8
spec/routing/webhooks_controller_spec.rb

@@ -1,23 +1,23 @@
1 1
 require 'spec_helper'
2 2
 
3
-describe "routing for web requests" do
3
+describe "routing for web requests", :type => :routing do
4 4
   it "routes to handle_request" do
5 5
     resulting_params = { :user_id => "6", :agent_id => "2", :secret => "foobar" }
6
-    get("/users/6/web_requests/2/foobar").should route_to("web_requests#handle_request", resulting_params)
7
-    post("/users/6/web_requests/2/foobar").should route_to("web_requests#handle_request", resulting_params)
8
-    put("/users/6/web_requests/2/foobar").should route_to("web_requests#handle_request", resulting_params)
9
-    delete("/users/6/web_requests/2/foobar").should route_to("web_requests#handle_request", resulting_params)
6
+    expect(get("/users/6/web_requests/2/foobar")).to route_to("web_requests#handle_request", resulting_params)
7
+    expect(post("/users/6/web_requests/2/foobar")).to route_to("web_requests#handle_request", resulting_params)
8
+    expect(put("/users/6/web_requests/2/foobar")).to route_to("web_requests#handle_request", resulting_params)
9
+    expect(delete("/users/6/web_requests/2/foobar")).to route_to("web_requests#handle_request", resulting_params)
10 10
   end
11 11
 
12 12
   it "supports the legacy /webhooks/ route" do
13
-    post("/users/6/webhooks/2/foobar").should route_to("web_requests#handle_request", :user_id => "6", :agent_id => "2", :secret => "foobar")
13
+    expect(post("/users/6/webhooks/2/foobar")).to route_to("web_requests#handle_request", :user_id => "6", :agent_id => "2", :secret => "foobar")
14 14
   end
15 15
 
16 16
   it "routes with format" do
17
-    get("/users/6/web_requests/2/foobar.json").should route_to("web_requests#handle_request",
17
+    expect(get("/users/6/web_requests/2/foobar.json")).to route_to("web_requests#handle_request",
18 18
                                                            { :user_id => "6", :agent_id => "2", :secret => "foobar", :format => "json" })
19 19
 
20
-    get("/users/6/web_requests/2/foobar.atom").should route_to("web_requests#handle_request",
20
+    expect(get("/users/6/web_requests/2/foobar.atom")).to route_to("web_requests#handle_request",
21 21
                                                            { :user_id => "6", :agent_id => "2", :secret => "foobar", :format => "atom" })
22 22
   end
23 23
 end

+ 1 - 1
spec/spec_helper.rb

@@ -53,7 +53,7 @@ RSpec.configure do |config|
53 53
 
54 54
   config.render_views
55 55
 
56
-  config.include Devise::TestHelpers, :type => :controller
56
+  config.include Devise::TestHelpers, type: :controller
57 57
   config.include SpecHelpers
58 58
   config.include Delorean
59 59
 end

+ 18 - 18
spec/support/shared_examples/email_concern.rb

@@ -16,73 +16,73 @@ shared_examples_for EmailConcern do
16 16
 
17 17
   describe "validations" do
18 18
     it "should be valid" do
19
-      agent.should be_valid
19
+      expect(agent).to be_valid
20 20
     end
21 21
 
22 22
     it "should validate the presence of 'subject'" do
23 23
       agent.options['subject'] = ''
24
-      agent.should_not be_valid
24
+      expect(agent).not_to be_valid
25 25
 
26 26
       agent.options['subject'] = nil
27
-      agent.should_not be_valid
27
+      expect(agent).not_to be_valid
28 28
     end
29 29
 
30 30
     it "should validate the presence of 'expected_receive_period_in_days'" do
31 31
       agent.options['expected_receive_period_in_days'] = ''
32
-      agent.should_not be_valid
32
+      expect(agent).not_to be_valid
33 33
 
34 34
       agent.options['expected_receive_period_in_days'] = nil
35
-      agent.should_not be_valid
35
+      expect(agent).not_to be_valid
36 36
     end
37 37
 
38 38
     it "should validate that recipients, when provided, is one or more valid email addresses" do
39 39
       agent.options['recipients'] = ''
40
-      agent.should be_valid
40
+      expect(agent).to be_valid
41 41
 
42 42
       agent.options['recipients'] = nil
43
-      agent.should be_valid
43
+      expect(agent).to be_valid
44 44
 
45 45
       agent.options['recipients'] = 'bob@example.com'
46
-      agent.should be_valid
46
+      expect(agent).to be_valid
47 47
 
48 48
       agent.options['recipients'] = ['bob@example.com']
49
-      agent.should be_valid
49
+      expect(agent).to be_valid
50 50
 
51 51
       agent.options['recipients'] = ['bob@example.com', 'jane@example.com']
52
-      agent.should be_valid
52
+      expect(agent).to be_valid
53 53
 
54 54
       agent.options['recipients'] = ['bob@example.com', 'example.com']
55
-      agent.should_not be_valid
55
+      expect(agent).not_to be_valid
56 56
 
57 57
       agent.options['recipients'] = ['hi!']
58
-      agent.should_not be_valid
58
+      expect(agent).not_to be_valid
59 59
 
60 60
       agent.options['recipients'] = { :foo => "bar" }
61
-      agent.should_not be_valid
61
+      expect(agent).not_to be_valid
62 62
 
63 63
       agent.options['recipients'] = "wut"
64
-      agent.should_not be_valid
64
+      expect(agent).not_to be_valid
65 65
     end
66 66
   end
67 67
 
68 68
   describe "#recipients" do
69 69
     it "defaults to the user's email address" do
70
-      agent.recipients.should == [users(:jane).email]
70
+      expect(agent.recipients).to eq([users(:jane).email])
71 71
     end
72 72
 
73 73
     it "wraps a string with an array" do
74 74
       agent.options['recipients'] = 'bob@bob.com'
75
-      agent.recipients.should == ['bob@bob.com']
75
+      expect(agent.recipients).to eq(['bob@bob.com'])
76 76
     end
77 77
 
78 78
     it "handles an array" do
79 79
       agent.options['recipients'] = ['bob@bob.com', 'jane@jane.com']
80
-      agent.recipients.should == ['bob@bob.com', 'jane@jane.com']
80
+      expect(agent.recipients).to eq(['bob@bob.com', 'jane@jane.com'])
81 81
     end
82 82
 
83 83
     it "interpolates" do
84 84
       agent.options['recipients'] = "{{ username }}@{{ domain }}"
85
-      agent.recipients('username' => 'bob', 'domain' => 'example.com').should == ["bob@example.com"]
85
+      expect(agent.recipients('username' => 'bob', 'domain' => 'example.com')).to eq(["bob@example.com"])
86 86
     end
87 87
   end
88 88
 end

+ 3 - 3
spec/support/shared_examples/has_guid.rb

@@ -3,10 +3,10 @@ require 'spec_helper'
3 3
 shared_examples_for HasGuid do
4 4
   it "gets created before_save, but only if it's not present" do
5 5
     instance = new_instance
6
-    instance.guid.should be_nil
6
+    expect(instance.guid).to be_nil
7 7
     instance.save!
8
-    instance.guid.should_not be_nil
8
+    expect(instance.guid).not_to be_nil
9 9
 
10
-    lambda { instance.save! }.should_not change { instance.reload.guid }
10
+    expect { instance.save! }.not_to change { instance.reload.guid }
11 11
   end
12 12
 end

+ 15 - 15
spec/support/shared_examples/liquid_interpolatable.rb

@@ -22,72 +22,72 @@ shared_examples_for LiquidInterpolatable do
22 22
 
23 23
   describe "interpolating liquid templates" do
24 24
     it "should work" do
25
-      @checker.interpolate_options(@checker.options, @event).should == {
25
+      expect(@checker.interpolate_options(@checker.options, @event)).to eq({
26 26
           "normal" => "just some normal text",
27 27
           "variable" => "hello",
28 28
           "text" => "Some test with an embedded hello",
29 29
           "escape" => "This should be Hello+world"
30
-      }
30
+      })
31 31
     end
32 32
 
33 33
     it "should work with arrays", focus: true do
34 34
       @checker.options = {"value" => ["{{variable}}", "Much array", "Hey, {{hello_world}}"]}
35
-      @checker.interpolate_options(@checker.options, @event).should == {
35
+      expect(@checker.interpolate_options(@checker.options, @event)).to eq({
36 36
         "value" => ["hello", "Much array", "Hey, Hello world"]
37
-      }
37
+      })
38 38
     end
39 39
 
40 40
     it "should work recursively" do
41 41
       @checker.options['hash'] = {'recursive' => "{{variable}}"}
42 42
       @checker.options['indifferent_hash'] = ActiveSupport::HashWithIndifferentAccess.new({'recursive' => "{{variable}}"})
43
-      @checker.interpolate_options(@checker.options, @event).should == {
43
+      expect(@checker.interpolate_options(@checker.options, @event)).to eq({
44 44
           "normal" => "just some normal text",
45 45
           "variable" => "hello",
46 46
           "text" => "Some test with an embedded hello",
47 47
           "escape" => "This should be Hello+world",
48 48
           "hash" => {'recursive' => 'hello'},
49 49
           "indifferent_hash" => {'recursive' => 'hello'},
50
-      }
50
+      })
51 51
     end
52 52
 
53 53
     it "should work for strings" do
54
-      @checker.interpolate_string("{{variable}}", @event).should == "hello"
55
-      @checker.interpolate_string("{{variable}} you", @event).should == "hello you"
54
+      expect(@checker.interpolate_string("{{variable}}", @event)).to eq("hello")
55
+      expect(@checker.interpolate_string("{{variable}} you", @event)).to eq("hello you")
56 56
     end
57 57
 
58 58
     it "should use local variables while in a block" do
59 59
       @checker.options['locals'] = '{{_foo_}} {{_bar_}}'
60 60
 
61 61
       @checker.interpolation_context.tap { |context|
62
-        @checker.interpolated['locals'].should == ' '
62
+        expect(@checker.interpolated['locals']).to eq(' ')
63 63
 
64 64
         context.stack {
65 65
           context['_foo_'] = 'This is'
66 66
           context['_bar_'] = 'great.'
67 67
 
68
-          @checker.interpolated['locals'].should == 'This is great.'
68
+          expect(@checker.interpolated['locals']).to eq('This is great.')
69 69
         }
70 70
 
71
-        @checker.interpolated['locals'].should == ' '
71
+        expect(@checker.interpolated['locals']).to eq(' ')
72 72
       }
73 73
     end
74 74
 
75 75
     it "should use another self object while in a block" do
76 76
       @checker.options['properties'] = '{{_foo_}} {{_bar_}}'
77 77
 
78
-      @checker.interpolated['properties'].should == ' '
78
+      expect(@checker.interpolated['properties']).to eq(' ')
79 79
 
80 80
       @checker.interpolate_with({ '_foo_' => 'That was', '_bar_' => 'nice.' }) {
81
-        @checker.interpolated['properties'].should == 'That was nice.'
81
+        expect(@checker.interpolated['properties']).to eq('That was nice.')
82 82
       }
83 83
 
84
-      @checker.interpolated['properties'].should == ' '
84
+      expect(@checker.interpolated['properties']).to eq(' ')
85 85
     end
86 86
   end
87 87
 
88 88
   describe "liquid tags" do
89 89
     it "should work with existing credentials" do
90
-      @checker.interpolate_string("{% credential aws_key %}", {}).should == '2222222222-jane'
90
+      expect(@checker.interpolate_string("{% credential aws_key %}", {})).to eq('2222222222-jane')
91 91
     end
92 92
 
93 93
     it "should raise an exception for undefined credentials" do

+ 19 - 19
spec/support/shared_examples/web_request_concern.rb

@@ -9,58 +9,58 @@ shared_examples_for WebRequestConcern do
9 9
 
10 10
   describe "validations" do
11 11
     it "should be valid" do
12
-      agent.should be_valid
12
+      expect(agent).to be_valid
13 13
     end
14 14
 
15 15
     it "should validate user_agent" do
16 16
       agent.options['user_agent'] = nil
17
-      agent.should be_valid
17
+      expect(agent).to be_valid
18 18
 
19 19
       agent.options['user_agent'] = ""
20
-      agent.should be_valid
20
+      expect(agent).to be_valid
21 21
 
22 22
       agent.options['user_agent'] = "foo"
23
-      agent.should be_valid
23
+      expect(agent).to be_valid
24 24
 
25 25
       agent.options['user_agent'] = ["foo"]
26
-      agent.should_not be_valid
26
+      expect(agent).not_to be_valid
27 27
 
28 28
       agent.options['user_agent'] = 1
29
-      agent.should_not be_valid
29
+      expect(agent).not_to be_valid
30 30
     end
31 31
 
32 32
     it "should validate headers" do
33 33
       agent.options['headers'] = "blah"
34
-      agent.should_not be_valid
34
+      expect(agent).not_to be_valid
35 35
 
36 36
       agent.options['headers'] = ""
37
-      agent.should be_valid
37
+      expect(agent).to be_valid
38 38
 
39 39
       agent.options['headers'] = {}
40
-      agent.should be_valid
40
+      expect(agent).to be_valid
41 41
 
42 42
       agent.options['headers'] = { 'foo' => 'bar' }
43
-      agent.should be_valid
43
+      expect(agent).to be_valid
44 44
     end
45 45
 
46 46
     it "should validate basic_auth" do
47 47
       agent.options['basic_auth'] = "foo:bar"
48
-      agent.should be_valid
48
+      expect(agent).to be_valid
49 49
 
50 50
       agent.options['basic_auth'] = ["foo", "bar"]
51
-      agent.should be_valid
51
+      expect(agent).to be_valid
52 52
 
53 53
       agent.options['basic_auth'] = ""
54
-      agent.should be_valid
54
+      expect(agent).to be_valid
55 55
 
56 56
       agent.options['basic_auth'] = nil
57
-      agent.should be_valid
57
+      expect(agent).to be_valid
58 58
 
59 59
       agent.options['basic_auth'] = "blah"
60
-      agent.should_not be_valid
60
+      expect(agent).not_to be_valid
61 61
 
62 62
       agent.options['basic_auth'] = ["blah"]
63
-      agent.should_not be_valid
63
+      expect(agent).not_to be_valid
64 64
     end
65 65
   end
66 66
 
@@ -75,17 +75,17 @@ shared_examples_for WebRequestConcern do
75 75
     end
76 76
 
77 77
     it "should have the default value set by Faraday" do
78
-      agent.user_agent.should == Faraday.new.headers[:user_agent]
78
+      expect(agent.user_agent).to eq(Faraday.new.headers[:user_agent])
79 79
     end
80 80
 
81 81
     it "should be overridden by the environment variable if present" do
82 82
       ENV['DEFAULT_HTTP_USER_AGENT'] = 'Huginn - https://github.com/cantino/huginn'
83
-      agent.user_agent.should == 'Huginn - https://github.com/cantino/huginn'
83
+      expect(agent.user_agent).to eq('Huginn - https://github.com/cantino/huginn')
84 84
     end
85 85
 
86 86
     it "should be overriden by the value in options if present" do
87 87
       agent.options['user_agent'] = 'Override'
88
-      agent.user_agent.should == 'Override'
88
+      expect(agent.user_agent).to eq('Override')
89 89
     end
90 90
   end
91 91
 end

+ 9 - 9
spec/support/shared_examples/working_helpers.rb

@@ -7,23 +7,23 @@ shared_examples_for WorkingHelpers do
7 7
 
8 8
       agent.last_error_log_at = 10.minutes.ago
9 9
       agent.last_event_at = 10.minutes.ago
10
-      agent.recent_error_logs?.should be_truthy
10
+      expect(agent.recent_error_logs?).to be_truthy
11 11
 
12 12
       agent.last_error_log_at = 11.minutes.ago
13 13
       agent.last_event_at = 10.minutes.ago
14
-      agent.recent_error_logs?.should be_truthy
14
+      expect(agent.recent_error_logs?).to be_truthy
15 15
 
16 16
       agent.last_error_log_at = 5.minutes.ago
17 17
       agent.last_event_at = 10.minutes.ago
18
-      agent.recent_error_logs?.should be_truthy
18
+      expect(agent.recent_error_logs?).to be_truthy
19 19
 
20 20
       agent.last_error_log_at = 15.minutes.ago
21 21
       agent.last_event_at = 10.minutes.ago
22
-      agent.recent_error_logs?.should be_falsey
22
+      expect(agent.recent_error_logs?).to be_falsey
23 23
 
24 24
       agent.last_error_log_at = 2.days.ago
25 25
       agent.last_event_at = 10.minutes.ago
26
-      agent.recent_error_logs?.should be_falsey
26
+      expect(agent.recent_error_logs?).to be_falsey
27 27
     end
28 28
   end
29 29
 
@@ -33,21 +33,21 @@ shared_examples_for WorkingHelpers do
33 33
     end
34 34
 
35 35
     it "should return false until the first event was received" do
36
-      @agent.received_event_without_error?.should == false
36
+      expect(@agent.received_event_without_error?).to eq(false)
37 37
       @agent.last_receive_at = Time.now
38
-      @agent.received_event_without_error?.should == true
38
+      expect(@agent.received_event_without_error?).to eq(true)
39 39
     end
40 40
 
41 41
     it "should return false when the last error occured after the last received event" do
42 42
       @agent.last_receive_at = Time.now - 1.minute
43 43
       @agent.last_error_log_at = Time.now
44
-      @agent.received_event_without_error?.should == false
44
+      expect(@agent.received_event_without_error?).to eq(false)
45 45
     end
46 46
 
47 47
     it "should return true when the last received event occured after the last error" do
48 48
       @agent.last_receive_at = Time.now
49 49
       @agent.last_error_log_at = Time.now - 1.minute
50
-      @agent.received_event_without_error?.should == true
50
+      expect(@agent.received_event_without_error?).to eq(true)
51 51
     end
52 52
   end
53 53
 end

Merge branch 'master' into do_not_symbolize · 73e8406d4f - Gogs J1X

Merge branch 'master' into do_not_symbolize

Conflicts:
app/models/agents/human_task_agent.rb
spec/models/agents/human_task_agent_spec.rb

Andrew Cantino 11 years ago
parent
commit
73e8406d4f

+ 3 - 3
Gemfile.lock

@@ -63,9 +63,9 @@ GEM
63 63
       railties (>= 3.2.6, < 5)
64 64
       warden (~> 1.2.3)
65 65
     diff-lcs (1.2.4)
66
-    dotenv (0.8.0)
67
-    dotenv-rails (0.8.0)
68
-      dotenv (= 0.8.0)
66
+    dotenv (0.9.0)
67
+    dotenv-rails (0.9.0)
68
+      dotenv (= 0.9.0)
69 69
     em-http-request (1.0.3)
70 70
       addressable (>= 2.2.3)
71 71
       cookiejar

+ 198 - 45
app/models/agents/human_task_agent.rb

@@ -9,9 +9,13 @@ module Agents
9 9
 
10 10
       HITs can be created in response to events, or on a schedule.  Set `trigger_on` to either `schedule` or `event`.
11 11
 
12
+      # Schedule
13
+
12 14
       The schedule of this Agent is how often it should check for completed HITs, __NOT__ how often to submit one.  To configure how often a new HIT
13 15
       should be submitted when in `schedule` mode, set `submission_period` to a number of hours.
14 16
 
17
+      # Example
18
+
15 19
       If created with an event, all HIT fields can contain interpolated values via [JSONPaths](http://goessner.net/articles/JsonPath/) placed between < and > characters.
16 20
       For example, if the incoming event was a Twitter event, you could make a HITT to rate its sentiment like this:
17 21
 
@@ -58,8 +62,52 @@ module Agents
58 62
       which contain `key` and `text`.  For _free\\_text_, the special configuration options are all optional, and are
59 63
       `default`, `min_length`, and `max_length`.
60 64
 
61
-      If all of the `questions` are of `type` _selection_, you can set `take_majority` to _true_ at the top level to
62
-      automatically select the majority vote for each question across all `assignments`.  If all selections are numeric, an `average_answer` will also be generated.
65
+      # Combining answers
66
+
67
+      There are a couple of ways to combine HITs that have multiple `assignments`, all of which involve setting `combination_mode` at the top level.
68
+
69
+      ## Taking the majority
70
+
71
+      Option 1: if all of your `questions` are of `type` _selection_, you can set `combination_mode` to `take_majority`.
72
+      This will cause the Agent to automatically select the majority vote for each question across all `assignments` and return it as `majority_answer`.
73
+      If all selections are numeric, an `average_answer` will also be generated.
74
+
75
+      Option 2: you can have the Agent ask additional human workers to rank the `assignments` and return the most highly ranked answer.
76
+      To do this, set `combination_mode` to `poll` and provide a `poll_options` object.  Here is an example:
77
+
78
+          {
79
+            "trigger_on": "schedule",
80
+            "submission_period": 12,
81
+            "combination_mode": "poll",
82
+            "poll_options": {
83
+              "title": "Take a poll about some jokes",
84
+              "instructions": "Please rank these jokes from most funny (5) to least funny (1)",
85
+              "assignments": 3,
86
+              "row_template": "<$.joke>"
87
+            },
88
+            "hit": {
89
+              "assignments": 5,
90
+              "title": "Tell a joke",
91
+              "description": "Please tell me a joke",
92
+              "reward": 0.05,
93
+              "lifetime_in_seconds": "3600",
94
+              "questions": [
95
+                {
96
+                  "type": "free_text",
97
+                  "key": "joke",
98
+                  "name": "Your joke",
99
+                  "required": "true",
100
+                  "question": "Joke",
101
+                  "min_length": "2",
102
+                  "max_length": "2000"
103
+                }
104
+              ]
105
+            }
106
+          }
107
+
108
+      Resulting events will have the original `answers`, as well as the `poll` results, and a field called `best_answer` that contains the best answer as determined by the poll.
109
+
110
+      # Other settings
63 111
 
64 112
       `lifetime_in_seconds` is the number of seconds a HIT is left on Amazon before it's automatically closed.  The default is 1 day.
65 113
 
@@ -70,6 +118,12 @@ module Agents
70 118
       Events look like:
71 119
 
72 120
           {
121
+            "answers": [
122
+              {
123
+                "feedback": "Hello!",
124
+                "sentiment": "happy"
125
+              }
126
+            ]
73 127
           }
74 128
     MD
75 129
 
@@ -97,9 +151,13 @@ module Agents
97 151
         errors.add(:base, "all questions of type 'selection' must have a selections array with selections that set 'key' and 'name'")
98 152
       end
99 153
 
100
-      if options['take_majority'] == "true" && options['hit']['questions'].any? { |question| question['type'] != "selection" }
154
+      if take_majority? && options['hit']['questions'].any? { |question| question['type'] != "selection" }
101 155
         errors.add(:base, "all questions must be of type 'selection' to use the 'take_majority' option")
102 156
       end
157
+
158
+      if create_poll?
159
+        errors.add(:base, "poll_options is required when combination_mode is set to 'poll' and must have the keys 'title', 'instructions', 'row_template', and 'assignments'") unless options['poll_options'].is_a?(Hash) && options['poll_options']['title'].present? && options['poll_options']['instructions'].present? && options['poll_options']['row_template'].present? && options['poll_options']['assignments'].to_i > 0
160
+      end
103 161
     end
104 162
 
105 163
     def default_options
@@ -152,69 +210,151 @@ module Agents
152 210
 
153 211
       if options['trigger_on'] == "schedule" && (memory['last_schedule'] || 0) <= Time.now.to_i - options['submission_period'].to_i * 60 * 60
154 212
         memory['last_schedule'] = Time.now.to_i
155
-        create_hit
213
+        create_basic_hit
156 214
       end
157 215
     end
158 216
 
159 217
     def receive(incoming_events)
160 218
       if options['trigger_on'] == "event"
161 219
         incoming_events.each do |event|
162
-          create_hit event
220
+          create_basic_hit event
163 221
         end
164 222
       end
165 223
     end
166 224
 
167 225
     protected
168 226
 
227
+    def take_majority?
228
+      options['combination_mode'] == "take_majority" || options['take_majority'] == "true"
229
+    end
230
+
231
+    def create_poll?
232
+      options['combination_mode'] == "poll"
233
+    end
234
+
235
+    def event_for_hit(hit_id)
236
+      if memory['hits'][hit_id].is_a?(Hash)
237
+        Event.find_by_id(memory['hits'][hit_id]['event_id'])
238
+      else
239
+        nil
240
+      end
241
+    end
242
+
243
+    def hit_type(hit_id)
244
+      if memory['hits'][hit_id].is_a?(Hash) && memory['hits'][hit_id]['type']
245
+        memory['hits'][hit_id]['type']
246
+      else
247
+        'user'
248
+      end
249
+    end
250
+
169 251
     def review_hits
170 252
       reviewable_hit_ids = RTurk::GetReviewableHITs.create.hit_ids
171 253
       my_reviewed_hit_ids = reviewable_hit_ids & (memory['hits'] || {}).keys
172 254
       if reviewable_hit_ids.length > 0
173 255
         log "MTurk reports #{reviewable_hit_ids.length} HITs, of which I own [#{my_reviewed_hit_ids.to_sentence}]"
174 256
       end
257
+
175 258
       my_reviewed_hit_ids.each do |hit_id|
176 259
         hit = RTurk::Hit.new(hit_id)
177 260
         assignments = hit.assignments
178 261
 
179 262
         log "Looking at HIT #{hit_id}.  I found #{assignments.length} assignments#{" with the statuses: #{assignments.map(&:status).to_sentence}" if assignments.length > 0}"
180 263
         if assignments.length == hit.max_assignments && assignments.all? { |assignment| assignment.status == "Submitted" }
181
-          payload = { 'answers' => assignments.map(&:answers) }
182
-
183
-          if options['take_majority'] == "true"
184
-            counts = {}
185
-            options['hit']['questions'].each do |question|
186
-              question_counts = question['selections'].inject({}) { |memo, selection| memo[selection['key']] = 0; memo }
187
-              assignments.each do |assignment|
188
-                answers = ActiveSupport::HashWithIndifferentAccess.new(assignment.answers)
189
-                answer = answers[question['key']]
190
-                question_counts[answer] += 1
264
+          inbound_event = event_for_hit(hit_id)
265
+
266
+          if hit_type(hit_id) == 'poll'
267
+            # handle completed polls
268
+
269
+            log "Handling a poll: #{hit_id}"
270
+
271
+            scores = {}
272
+            assignments.each do |assignment|
273
+              assignment.answers.each do |index, rating|
274
+                scores[index] ||= 0
275
+                scores[index] += rating.to_i
191 276
               end
192
-              counts[question['key']] = question_counts
193 277
             end
194
-            payload['counts'] = counts
195 278
 
196
-            majority_answer = counts.inject({}) do |memo, (key, question_counts)|
197
-              memo[key] = question_counts.to_a.sort {|a, b| a.last <=> b.last }.last.first
198
-              memo
199
-            end
200
-            payload['majority_answer'] = majority_answer
201
-
202
-            if all_questions_are_numeric?
203
-              average_answer = counts.inject({}) do |memo, (key, question_counts)|
204
-                sum = divisor = 0
205
-                question_counts.to_a.each do |num, count|
206
-                  sum += num.to_s.to_f * count
207
-                  divisor += count
279
+            top_answer = scores.to_a.sort {|b, a| a.last <=> b.last }.first.first
280
+
281
+            payload = {
282
+              'answers' => memory['hits'][hit_id]['answers'],
283
+              'poll' => assignments.map(&:answers),
284
+              'best_answer' => memory['hits'][hit_id]['answers'][top_answer.to_i - 1]
285
+            }
286
+
287
+            event = create_event :payload => payload
288
+            log "Event emitted with answer(s) for poll", :outbound_event => event, :inbound_event => inbound_event
289
+          else
290
+            # handle normal completed HITs
291
+            payload = { 'answers' => assignments.map(&:answers) }
292
+
293
+            if take_majority?
294
+              counts = {}
295
+              options['hit']['questions'].each do |question|
296
+                question_counts = question['selections'].inject({}) { |memo, selection| memo[selection['key']] = 0; memo }
297
+                assignments.each do |assignment|
298
+                  answers = ActiveSupport::HashWithIndifferentAccess.new(assignment.answers)
299
+                  answer = answers[question['key']]
300
+                  question_counts[answer] += 1
208 301
                 end
209
-                memo[key] = sum / divisor.to_f
302
+                counts[question['key']] = question_counts
303
+              end
304
+              payload['counts'] = counts
305
+
306
+              majority_answer = counts.inject({}) do |memo, (key, question_counts)|
307
+                memo[key] = question_counts.to_a.sort {|a, b| a.last <=> b.last }.last.first
210 308
                 memo
211 309
               end
212
-              payload['average_answer'] = average_answer
310
+              payload['majority_answer'] = majority_answer
311
+
312
+              if all_questions_are_numeric?
313
+                average_answer = counts.inject({}) do |memo, (key, question_counts)|
314
+                  sum = divisor = 0
315
+                  question_counts.to_a.each do |num, count|
316
+                    sum += num.to_s.to_f * count
317
+                    divisor += count
318
+                  end
319
+                  memo[key] = sum / divisor.to_f
320
+                  memo
321
+                end
322
+                payload['average_answer'] = average_answer
323
+              end
213 324
             end
214
-          end
215 325
 
216
-          event = create_event :payload => payload
217
-          log "Event emitted with answer(s)", :outbound_event => event, :inbound_event => Event.find_by_id(memory['hits'][hit_id])
326
+            if create_poll?
327
+              questions = []
328
+              selections = 5.times.map { |i| { 'key' => i+1, 'text' => i+1 } }.reverse
329
+              assignments.length.times do |index|
330
+                questions << {
331
+                  'type' => "selection",
332
+                  'name' => "Item #{index + 1}",
333
+                  'key' => index,
334
+                  'required' => "true",
335
+                  'question' => Utils.interpolate_jsonpaths(options['poll_options']['row_template'], assignments[index].answers),
336
+                  'selections' => selections
337
+                }
338
+              end
339
+
340
+              poll_hit = create_hit 'title' => options['poll_options']['title'],
341
+                                    'description' => options['poll_options']['instructions'],
342
+                                    'questions' => questions,
343
+                                    'assignments' => options['poll_options']['assignments'],
344
+                                    'lifetime_in_seconds' => options['poll_options']['lifetime_in_seconds'],
345
+                                    'reward' => options['poll_options']['reward'],
346
+                                    'payload' => inbound_event && inbound_event.payload,
347
+                                    'metadata' => { 'type' => 'poll',
348
+                                                    'original_hit' => hit_id,
349
+                                                    'answers' => assignments.map(&:answers),
350
+                                                    'event_id' => inbound_event && inbound_event.id }
351
+
352
+              log "Poll HIT created with ID #{poll_hit.id} and URL #{poll_hit.url}.  Original HIT: #{hit_id}", :inbound_event => inbound_event
353
+            else
354
+              event = create_event :payload => payload
355
+              log "Event emitted with answer(s)", :outbound_event => event, :inbound_event => inbound_event
356
+            end
357
+          end
218 358
 
219 359
           assignments.each(&:approve!)
220 360
           hit.dispose!
@@ -232,22 +372,35 @@ module Agents
232 372
       end
233 373
     end
234 374
 
235
-    def create_hit(event = nil)
236
-      payload = event ? event.payload : {}
237
-      title = Utils.interpolate_jsonpaths(options['hit']['title'], payload).strip
238
-      description = Utils.interpolate_jsonpaths(options['hit']['description'], payload).strip
239
-      questions = Utils.recursively_interpolate_jsonpaths(options['hit']['questions'], payload)
375
+    def create_basic_hit(event = nil)
376
+      hit = create_hit 'title' => options['hit']['title'],
377
+                       'description' => options['hit']['description'],
378
+                       'questions' => options['hit']['questions'],
379
+                       'assignments' => options['hit']['assignments'],
380
+                       'lifetime_in_seconds' => options['hit']['lifetime_in_seconds'],
381
+                       'reward' => options['hit']['reward'],
382
+                       'payload' => event && event.payload,
383
+                       'metadata' => { 'event_id' => event && event.id }
384
+
385
+      log "HIT created with ID #{hit.id} and URL #{hit.url}", :inbound_event => event
386
+    end
387
+
388
+    def create_hit(opts = {})
389
+      payload = opts['payload'] || {}
390
+      title = Utils.interpolate_jsonpaths(opts['title'], payload).strip
391
+      description = Utils.interpolate_jsonpaths(opts['description'], payload).strip
392
+      questions = Utils.recursively_interpolate_jsonpaths(opts['questions'], payload)
240 393
       hit = RTurk::Hit.create(:title => title) do |hit|
241
-        hit.max_assignments = (options['hit']['assignments'] || 1).to_i
394
+        hit.max_assignments = (opts['assignments'] || 1).to_i
242 395
         hit.description = description
243
-        hit.lifetime = (options['hit']['lifetime_in_seconds'] || 24 * 60 * 60).to_i
396
+        hit.lifetime = (opts['lifetime_in_seconds'] || 24 * 60 * 60).to_i
244 397
         hit.question_form AgentQuestionForm.new(:title => title, :description => description, :questions => questions)
245
-        hit.reward = (options['hit']['reward'] || 0.05).to_f
398
+        hit.reward = (opts['reward'] || 0.05).to_f
246 399
         #hit.qualifications.add :approval_rate, { :gt => 80 }
247 400
       end
248 401
       memory['hits'] ||= {}
249
-      memory['hits'][hit.id] = event && event.id
250
-      log "HIT created with ID #{hit.id} and URL #{hit.url}", :inbound_event => event
402
+      memory['hits'][hit.id] = opts['metadata'] || {}
403
+      hit
251 404
     end
252 405
 
253 406
     # RTurk Question Form
@@ -328,4 +481,4 @@ module Agents
328 481
       end
329 482
     end
330 483
   end
331
-end
484
+end

+ 62 - 0
app/models/agents/webhook_agent.rb

@@ -0,0 +1,62 @@
1
+module Agents
2
+  class WebhookAgent < Agent
3
+    cannot_be_scheduled!
4
+
5
+    description  do
6
+        <<-MD
7
+        Use this Agent to create events by receiving webhooks from any source.
8
+
9
+        In order to create events with this agent, make a POST request to:
10
+        ```
11
+           https://#{ENV['DOMAIN']}/users/#{user.id}/webhooks/#{id || '<id>'}/:secret
12
+        ``` where `:secret` is specified in your options.
13
+
14
+        The
15
+
16
+        Options:
17
+
18
+          * `secret` - A token that the host will provide for authentication.
19
+          * `expected_receive_period_in_days` - How often you expect to receive
20
+            events this way. Used to determine if the agent is working.
21
+          * `payload_path` - JSONPath of the attribute in the POST body to be
22
+            used as the Event payload.
23
+      MD
24
+    end
25
+
26
+    event_description do
27
+      <<-MD
28
+        The event payload is base on the value of the `payload_path` option,
29
+        which is set to `#{options['payload_path']}`.
30
+      MD
31
+    end
32
+
33
+    def default_options
34
+      { "secret" => "supersecretstring",
35
+        "expected_receive_period_in_days" => 1,
36
+        "payload_path" => "payload"}
37
+    end
38
+
39
+    def receive_webhook(params)
40
+      secret = params.delete('secret')
41
+      return ["Not Authorized", 401] unless secret == options['secret']
42
+
43
+      create_event(:payload => payload_for(params))
44
+
45
+      ['Event Created', 201]
46
+    end
47
+
48
+    def working?
49
+      event_created_within(options['expected_receive_period_in_days']) && !recent_error_logs?
50
+    end
51
+
52
+    def validate_options
53
+      unless options['secret'].present?
54
+        errors.add(:base, "Must specify a secret for 'Authenticating' requests")
55
+      end
56
+    end
57
+
58
+    def payload_for(params)
59
+      Utils.value_at(params, options['payload_path']) || {}
60
+    end
61
+  end
62
+end

+ 30 - 1
lib/capistrano/sync.rb

@@ -1,5 +1,6 @@
1 1
 require 'yaml'
2 2
 require 'pathname'
3
+require 'dotenv'
3 4
 
4 5
 # Edited by Andrew Cantino.  Based on: https://gist.github.com/339471
5 6
 
@@ -99,6 +100,28 @@ namespace :sync do
99 100
     return database["#{db}"]['username'], database["#{db}"]['password'], database["#{db}"]['database'], database["#{db}"]['host']
100 101
   end
101 102
 
103
+  # Used by remote_database_config to parse the remote .env file.  Depends on the dotenv-rails gem.
104
+  class RemoteEnvLoader < Dotenv::Environment
105
+    def initialize(data)
106
+      @data = data
107
+      load
108
+    end
109
+
110
+    def with_loaded_env
111
+      begin
112
+        saved_env = ENV.to_hash.dup
113
+        ENV.update(self)
114
+        yield
115
+      ensure
116
+        ENV.replace(saved_env)
117
+      end
118
+    end
119
+
120
+    def read
121
+      @data.split("\n")
122
+    end
123
+  end
124
+
102 125
   #
103 126
   # Reads the database credentials from the remote config/database.yml file
104 127
   # +db+ the name of the environment to get the credentials for
@@ -106,7 +129,13 @@ namespace :sync do
106 129
   #
107 130
   def remote_database_config(db)
108 131
     remote_config = capture("cat #{current_path}/config/database.yml")
109
-    database = YAML::load(remote_config)
132
+    remote_env = capture("cat #{current_path}/.env")
133
+
134
+    database = nil
135
+    RemoteEnvLoader.new(remote_env).with_loaded_env do
136
+      database = YAML::load(ERB.new(remote_config).result)
137
+    end
138
+
110 139
     return database["#{db}"]['username'], database["#{db}"]['password'], database["#{db}"]['database'], database["#{db}"]['host']
111 140
   end
112 141
 

+ 172 - 19
spec/models/agents/human_task_agent_spec.rb

@@ -108,7 +108,43 @@ describe Agents::HumanTaskAgent do
108 108
       @checker.should_not be_valid
109 109
     end
110 110
 
111
-    it "requires that all questions be of type 'selection' when `take_majority` is `true`" do
111
+    it "requires that 'poll_options' be present and populated when 'combination_mode' is set to 'poll'" do
112
+      @checker.options['combination_mode'] = "poll"
113
+      @checker.should_not be_valid
114
+      @checker.options['poll_options'] = {}
115
+      @checker.should_not be_valid
116
+      @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
117
+                                           'instructions' => "Rank these by how funny they are",
118
+                                           'assignments' => 3,
119
+                                           'row_template' => "<$.joke>" }
120
+      @checker.should be_valid
121
+      @checker.options['poll_options'] = { 'instructions' => "Rank these by how funny they are",
122
+                                           'assignments' => 3,
123
+                                           'row_template' => "<$.joke>" }
124
+      @checker.should_not be_valid
125
+      @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
126
+                                           'assignments' => 3,
127
+                                           'row_template' => "<$.joke>" }
128
+      @checker.should_not be_valid
129
+      @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
130
+                                           'instructions' => "Rank these by how funny they are",
131
+                                           'row_template' => "<$.joke>" }
132
+      @checker.should_not be_valid
133
+      @checker.options['poll_options'] = { 'title' => "Take a poll about jokes",
134
+                                           'instructions' => "Rank these by how funny they are",
135
+                                           'assignments' => 3}
136
+      @checker.should_not be_valid
137
+    end
138
+
139
+    it "requires that all questions be of type 'selection' when 'combination_mode' is 'take_majority'" do
140
+      @checker.options['combination_mode'] = "take_majority"
141
+      @checker.should_not be_valid
142
+      @checker.options['hit']['questions'][1]['type'] = "selection"
143
+      @checker.options['hit']['questions'][1]['selections'] = @checker.options['hit']['questions'][0]['selections']
144
+      @checker.should be_valid
145
+    end
146
+
147
+    it "accepts 'take_majority': 'true' for legacy support" do
112 148
       @checker.options['take_majority'] = "true"
113 149
       @checker.should_not be_valid
114 150
       @checker.options['hit']['questions'][1]['type'] = "selection"
@@ -126,7 +162,7 @@ describe Agents::HumanTaskAgent do
126 162
 
127 163
     it "should check for reviewable HITs frequently" do
128 164
       mock(@checker).review_hits.twice
129
-      mock(@checker).create_hit.once
165
+      mock(@checker).create_basic_hit.once
130 166
       @checker.check
131 167
       @checker.check
132 168
     end
@@ -135,7 +171,7 @@ describe Agents::HumanTaskAgent do
135 171
       now = Time.now
136 172
       stub(Time).now { now }
137 173
       mock(@checker).review_hits.times(3)
138
-      mock(@checker).create_hit.twice
174
+      mock(@checker).create_basic_hit.twice
139 175
       @checker.check
140 176
       now += 1 * 60 * 60
141 177
       @checker.check
@@ -144,7 +180,7 @@ describe Agents::HumanTaskAgent do
144 180
     end
145 181
 
146 182
     it "should ignore events" do
147
-      mock(@checker).create_hit(anything).times(0)
183
+      mock(@checker).create_basic_hit(anything).times(0)
148 184
       @checker.receive([events(:bob_website_agent_event)])
149 185
     end
150 186
   end
@@ -155,7 +191,7 @@ describe Agents::HumanTaskAgent do
155 191
       now = Time.now
156 192
       stub(Time).now { now }
157 193
       mock(@checker).review_hits.times(3)
158
-      mock(@checker).create_hit.times(0)
194
+      mock(@checker).create_basic_hit.times(0)
159 195
       @checker.check
160 196
       now += 1 * 60 * 60
161 197
       @checker.check
@@ -164,7 +200,7 @@ describe Agents::HumanTaskAgent do
164 200
     end
165 201
 
166 202
     it "should create HITs based on events" do
167
-      mock(@checker).create_hit(events(:bob_website_agent_event)).times(1)
203
+      mock(@checker).create_basic_hit(events(:bob_website_agent_event)).times(1)
168 204
       @checker.receive([events(:bob_website_agent_event)])
169 205
     end
170 206
   end
@@ -181,7 +217,7 @@ describe Agents::HumanTaskAgent do
181 217
       mock(hitInterface).question_form(instance_of Agents::HumanTaskAgent::AgentQuestionForm) { |agent_question_form_instance| question_form = agent_question_form_instance }
182 218
       mock(RTurk::Hit).create(:title => "Hi Joe").yields(hitInterface) { hitInterface }
183 219
 
184
-      @checker.send :create_hit, @event
220
+      @checker.send :create_basic_hit, @event
185 221
 
186 222
       hitInterface.max_assignments.should == @checker.options['hit']['assignments']
187 223
       hitInterface.reward.should == @checker.options['hit']['reward']
@@ -192,7 +228,7 @@ describe Agents::HumanTaskAgent do
192 228
       xml.should include("<Text>Make something for Joe</Text>")
193 229
       xml.should include("<DisplayName>Joe Question 1</DisplayName>")
194 230
 
195
-      @checker.memory['hits'][123].should == @event.id
231
+      @checker.memory['hits'][123]['event_id'].should == @event.id
196 232
     end
197 233
 
198 234
     it "works without an event too" do
@@ -201,7 +237,7 @@ describe Agents::HumanTaskAgent do
201 237
       hitInterface.id = 123
202 238
       mock(hitInterface).question_form(instance_of Agents::HumanTaskAgent::AgentQuestionForm)
203 239
       mock(RTurk::Hit).create(:title => "Hi").yields(hitInterface) { hitInterface }
204
-      @checker.send :create_hit
240
+      @checker.send :create_basic_hit
205 241
       hitInterface.max_assignments.should == @checker.options['hit']['assignments']
206 242
       hitInterface.reward.should == @checker.options['hit']['reward']
207 243
     end
@@ -259,8 +295,8 @@ describe Agents::HumanTaskAgent do
259 295
 
260 296
       # It knows about two HITs from two different events.
261 297
       @checker.memory['hits'] = {}
262
-      @checker.memory['hits']["JH3132836336DHG"] = @event.id
263
-      @checker.memory['hits']["JH39AA63836DHG"] = event2.id
298
+      @checker.memory['hits']["JH3132836336DHG"] = { 'event_id' => @event.id }
299
+      @checker.memory['hits']["JH39AA63836DHG"] = { 'event_id' => event2.id }
264 300
 
265 301
       hit_ids = %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345]
266 302
       mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { hit_ids } } # It sees 3 HITs.
@@ -273,7 +309,7 @@ describe Agents::HumanTaskAgent do
273 309
     end
274 310
 
275 311
     it "shouldn't do anything if an assignment isn't ready" do
276
-      @checker.memory['hits'] = { "JH3132836336DHG" => @event.id }
312
+      @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
277 313
       mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
278 314
       assignments = [
279 315
         FakeAssignment.new(:status => "Accepted", :answers => {}),
@@ -288,11 +324,11 @@ describe Agents::HumanTaskAgent do
288 324
       @checker.send :review_hits
289 325
 
290 326
       assignments.all? {|a| a.approved == true }.should be_false
291
-      @checker.memory['hits'].should == { "JH3132836336DHG" => @event.id }
327
+      @checker.memory['hits'].should == { "JH3132836336DHG" => { 'event_id' => @event.id } }
292 328
     end
293 329
 
294 330
     it "shouldn't do anything if an assignment is missing" do
295
-      @checker.memory['hits'] = { "JH3132836336DHG" => @event.id }
331
+      @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
296 332
       mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
297 333
       assignments = [
298 334
         FakeAssignment.new(:status => "Submitted", :answers => {"sentiment"=>"happy", "feedback"=>"Take 2"})
@@ -306,11 +342,11 @@ describe Agents::HumanTaskAgent do
306 342
       @checker.send :review_hits
307 343
 
308 344
       assignments.all? {|a| a.approved == true }.should be_false
309
-      @checker.memory['hits'].should == { "JH3132836336DHG" => @event.id }
345
+      @checker.memory['hits'].should == { "JH3132836336DHG" => { 'event_id' => @event.id } }
310 346
     end
311 347
 
312 348
     it "should create events when all assignments are ready" do
313
-      @checker.memory['hits'] = { "JH3132836336DHG" => @event.id }
349
+      @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
314 350
       mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
315 351
       assignments = [
316 352
         FakeAssignment.new(:status => "Submitted", :answers => {"sentiment"=>"neutral", "feedback"=>""}),
@@ -337,8 +373,8 @@ describe Agents::HumanTaskAgent do
337 373
 
338 374
     describe "taking majority votes" do
339 375
       before do
340
-        @checker.options['take_majority'] = "true"
341
-        @checker.memory['hits'] = { "JH3132836336DHG" => @event.id }
376
+        @checker.options['combination_mode'] = "take_majority"
377
+        @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
342 378
         mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
343 379
       end
344 380
 
@@ -386,6 +422,10 @@ describe Agents::HumanTaskAgent do
386 422
       end
387 423
 
388 424
       it "should also provide an average answer when all questions are numeric" do
425
+        # it should accept 'take_majority': 'true' as well for legacy support.  Demonstrating that here.
426
+        @checker.options.delete :combination_mode
427
+        @checker.options['take_majority'] = "true"
428
+
389 429
         @checker.options['hit']['questions'] = [
390 430
           {
391 431
             'type' => "selection",
@@ -435,5 +475,118 @@ describe Agents::HumanTaskAgent do
435 475
         @checker.memory['hits'].should == {}
436 476
       end
437 477
     end
478
+
479
+    describe "creating and reviewing polls" do
480
+      before do
481
+        @checker.options['combination_mode'] = "poll"
482
+        @checker.options['poll_options'] = {
483
+          'title' => "Hi!",
484
+          'instructions' => "hello!",
485
+          'assignments' => 2,
486
+          'row_template' => "This is <.sentiment>"
487
+        }
488
+        @event.save!
489
+        mock(RTurk::GetReviewableHITs).create { mock!.hit_ids { %w[JH3132836336DHG JH39AA63836DHG JH39AA63836DH12345] } }
490
+      end
491
+
492
+      it "creates a poll using the row_template, message, and correct number of assignments" do
493
+        @checker.memory['hits'] = { "JH3132836336DHG" => { 'event_id' => @event.id } }
494
+
495
+        # Mock out the HIT's submitted assignments.
496
+        assignments = [
497
+          FakeAssignment.new(:status => "Submitted", :answers => {"sentiment"=>"sad",     "feedback"=>"This is my feedback 1"}),
498
+          FakeAssignment.new(:status => "Submitted", :answers => {"sentiment"=>"neutral", "feedback"=>"This is my feedback 2"}),
499
+          FakeAssignment.new(:status => "Submitted", :answers => {"sentiment"=>"happy",   "feedback"=>"This is my feedback 3"}),
500
+          FakeAssignment.new(:status => "Submitted", :answers => {"sentiment"=>"happy",   "feedback"=>"This is my feedback 4"})
501
+        ]
502
+        hit = FakeHit.new(:max_assignments => 4, :assignments => assignments)
503
+        mock(RTurk::Hit).new("JH3132836336DHG") { hit }
504
+
505
+        @checker.memory['hits']["JH3132836336DHG"].should be_present
506
+
507
+        # Setup mocks for HIT creation
508
+
509
+        question_form = nil
510
+        hitInterface = OpenStruct.new
511
+        hitInterface.id = "JH39AA63836DH12345"
512
+        mock(hitInterface).question_form(instance_of Agents::HumanTaskAgent::AgentQuestionForm) { |agent_question_form_instance| question_form = agent_question_form_instance }
513
+        mock(RTurk::Hit).create(:title => "Hi!").yields(hitInterface) { hitInterface }
514
+
515
+        # And finally, the test.
516
+
517
+        lambda {
518
+          @checker.send :review_hits
519
+        }.should change { Event.count }.by(0) # it does not emit an event until all poll results are in
520
+
521
+        # it approves the existing assignments
522
+
523
+        assignments.all? {|a| a.approved == true }.should be_true
524
+        hit.should be_disposed
525
+
526
+        # it creates a new HIT for the poll
527
+
528
+        hitInterface.max_assignments.should == @checker.options['poll_options']['assignments']
529
+        hitInterface.description.should == @checker.options['poll_options']['instructions']
530
+
531
+        xml = question_form.to_xml
532
+        xml.should include("<Text>This is happy</Text>")
533
+        xml.should include("<Text>This is neutral</Text>")
534
+        xml.should include("<Text>This is sad</Text>")
535
+
536
+        @checker.save
537
+        @checker.reload
538
+        @checker.memory['hits']["JH3132836336DHG"].should_not be_present
539
+        @checker.memory['hits']["JH39AA63836DH12345"].should be_present
540
+        @checker.memory['hits']["JH39AA63836DH12345"]['event_id'].should == @event.id
541
+        @checker.memory['hits']["JH39AA63836DH12345"]['type'].should == "poll"
542
+        @checker.memory['hits']["JH39AA63836DH12345"]['original_hit'].should == "JH3132836336DHG"
543
+        @checker.memory['hits']["JH39AA63836DH12345"]['answers'].length.should == 4
544
+      end
545
+
546
+      it "emits an event when all poll results are in, containing the data from the best answer, plus all others" do
547
+        original_answers = [
548
+          { 'sentiment' => "sad",     'feedback' => "This is my feedback 1"},
549
+          { 'sentiment' => "neutral", 'feedback' => "This is my feedback 2"},
550
+          { 'sentiment' => "happy",   'feedback' => "This is my feedback 3"},
551
+          { 'sentiment' => "happy",   'feedback' => "This is my feedback 4"}
552
+        ]
553
+
554
+        @checker.memory['hits'] = {
555
+          'JH39AA63836DH12345' => {
556
+            'type' => 'poll',
557
+            'original_hit' => "JH3132836336DHG",
558
+            'answers' => original_answers,
559
+            'event_id' => 345
560
+          }
561
+        }
562
+
563
+        # Mock out the HIT's submitted assignments.
564
+        assignments = [
565
+          FakeAssignment.new(:status => "Submitted", :answers => {"1" => "2", "2" => "5", "3" => "3", "4" => "2"}),
566
+          FakeAssignment.new(:status => "Submitted", :answers => {"1" => "3", "2" => "4", "3" => "1", "4" => "4"})
567
+        ]
568
+        hit = FakeHit.new(:max_assignments => 2, :assignments => assignments)
569
+        mock(RTurk::Hit).new("JH39AA63836DH12345") { hit }
570
+
571
+        @checker.memory['hits']["JH39AA63836DH12345"].should be_present
572
+
573
+        lambda {
574
+          @checker.send :review_hits
575
+        }.should change { Event.count }.by(1)
576
+
577
+        # It emits an event
578
+
579
+        @checker.events.last.payload['answers'].should == original_answers
580
+        @checker.events.last.payload['poll'].should == [{"1" => "2", "2" => "5", "3" => "3", "4" => "2"}, {"1" => "3", "2" => "4", "3" => "1", "4" => "4"}]
581
+        @checker.events.last.payload['best_answer'].should == {'sentiment' => "neutral", 'feedback' => "This is my feedback 2"}
582
+
583
+        # it approves the existing assignments
584
+
585
+        assignments.all? {|a| a.approved == true }.should be_true
586
+        hit.should be_disposed
587
+
588
+        @checker.memory['hits'].should be_empty
589
+      end
590
+    end
438 591
   end
439
-end
592
+end

+ 31 - 0
spec/models/agents/webhook_agent_spec.rb

@@ -0,0 +1,31 @@
1
+require 'spec_helper'
2
+
3
+describe Agents::WebhookAgent do
4
+  let(:agent) do
5
+    _agent = Agents::WebhookAgent.new(:name => 'webhook',
6
+                                      :options => { 'secret' => 'foobar', 'payload_path' => 'payload' })
7
+    _agent.user = users(:bob)
8
+    _agent.save!
9
+    _agent
10
+  end
11
+  let(:payload) { {'some' => 'info'} }
12
+
13
+  describe 'receive_webhook' do
14
+    it 'should create event if secret matches' do
15
+      out = nil
16
+      lambda {
17
+        out = agent.receive_webhook('secret' => 'foobar', 'payload' => payload)
18
+      }.should change { Event.count }.by(1)
19
+      out.should eq(['Event Created', 201])
20
+      Event.last.payload.should eq(payload)
21
+    end
22
+
23
+    it 'should not create event if secrets dont match' do
24
+      out = nil
25
+      lambda {
26
+        out = agent.receive_webhook('secret' => 'bazbat', 'payload' => payload)
27
+      }.should change { Event.count }.by(0)
28
+      out.should eq(['Not Authorized', 401])
29
+    end
30
+  end
31
+end